Citar un nombre de archivo en un script pasado a chmod?

Tengo problemas para citar un nombre de archivo que eventualmente se pasa a chmod. El script se escribió originalmente para SSH StrictModesy authorized_keys, pero tuve que expandirlo debido a un error UMASKque causó algunas molestias en el sistema de archivos. El guión está a continuación, y está produciendo montones de:

chmod: /Users/<user>/Library/Application: No such file or directory
chmod: Support/TextMate/Managed/Bundles/HTML.tmbundle/Macros/Delete: No such file or directory
chmod: whitespace: No such file or directory
chmod: between: No such file or directory
chmod: tags.plist: No such file or directory
...
chmod: /Users/<user>/Library/Caches/com.operasoftware.Opera/Media: No such file or directory
chmod: Cache/index: No such file or directory
cut: stdin: Illegal byte sequence
...

He intentado algunas soluciones alternativas, pero ninguna de ellas ha ayudado. Intenté usar comillas dobles como ""$file"", pero el problema persistió. También probé las marcas de retroceso (que se desaconsejan en este caso), pero el problema persistió.

Lo más cerca que estuve de citar fue lo disfuncional "\"""$file""\"". Pero luego chmodse quejó de que el nombre del archivo (con comillas) no era un archivo real:

$ sudo ~/fix-perms.sh
chmod: "/Users/Shared/.localized": No such file or directory
chmod: "/Users/<user>/.CFUserTextEncoding": No such file or directory
chmod: "/Users/<user>/.lesshst": No such file or directory

¿Cómo cito el nombre de archivo que sale del findque se pasa chmod? ¿O cómo puedo chmodtomar el nombre del archivo como un solo argumento?


$ cat ~/fix-perms.sh 
#!/bin/bash

# Directories
find /Users/* -type d -exec chmod 0700 {} \;
find /Users/Shared -type d -exec chmod 0777 {} \;

# Files
for file in `find /Users/* -type f`;
do
    if [ `file "$file" | cut -d":" -f 2 | grep -i -c executable` -eq 0 ];
    then
        `chmod 0600 "$file"`
    else
        `chmod 0700 "$file"`
    fi
done

for user in `ls -A /Users`;
do
    if [ -e "/Users/$user/.ssh/authorized_keys" ];
    then
        chmod 0600 "/Users/${user}/.ssh/authorized_keys"
    fi
done
¿Está invertido en bash scripting o hay otras opciones que aceptaría? Si eres bash, podría volver a trabajar en las cosas en find | xargs sudo chmodlugar de desenrollar todo en un bucle for donde los espacios te causan problemas para iterar sobre la variable.

Respuestas (4)

fwiw, find [options] -print0, cuando se usa junto con (conectado a) xargs -0 [options], maneja los nombres de archivo con espacios, sin alterar los forbucles o IFS:

find /Users ! -path '/Users/Shared*' -type f -print0 | xargs -0 -I {} \
    bash -c 'if [[ "$(file -b --mime-type -- "{}")" = "application/x-mach-binary" ]]; then
                 chmod 700 "{}"
             else
                 chmod 600 "{}"
             fi'
gracias @patrix, eso es mejor de lo que se me hubiera ocurrido.
Sí, hay tantas maneras de hacer cosas con el caparazón... (a veces lo encuentro frustrante).
Alternativa ligera: find /path -type d -maxdepth 1 ! -perm 0700 -exec chmod 0755 {} \; es -maxdepth #arbitraria, a veces no necesita o no quiere ir demasiado lejos en el árbol.

Pude resolver el problema cambiando IFSy IFS=$(echo -en "\n\b")no citando el nombre del archivo. IFSes Internal Field Separator , se usa (entre otros) para dividir palabras después de expansiones de shell, e incluye un espacio por defecto.

Encontré el IFStruco en BASH Shell: For Loop File Names With Spaces de nixCraft .

$ cat fix-perms.sh
#!/bin/bash

SAVED_IFS=$IFS
IFS=$(echo -en "\n\b")

# Directories
chmod 0755 /Users
find /Users/* -type d -exec chmod 0700 {} \;
find /Users/Shared -type d -exec chmod 0777 {} \;

# Files
for file in `find /Users/* -type f`;
do
    if [ `file "$file" | cut -d":" -f 2 | grep -i -c executable` -eq 0 ];
    then
        chmod 0600 "$file"
    else
        chmod 0700 "$file"
    fi
done

for user in `ls -A /Users`;
do
    if [ -e "/Users/$user/.ssh/authorized_keys" ];
    then
        chmod 0600 "/Users/${user}/.ssh/authorized_keys"
    fi
done

IFS=$SAVED_IFS
Realmente no puedo recomendar este método, ya que fallará para los nombres de archivo con saltos de línea (¿o campanas? ¿Por qué está ahí...?). Supongamos que un usuario creó un archivo llamado a\n*\nb: se analizará en tres palabras: "/Usuarios/algo/a", * y "b"; luego, el * se expandirá a una lista de archivos y subdirectorios en el directorio en el que se encuentra, y el script cambiará sus permisos (tal vez mal, ya que los subdirectorios no son ejecutables). Pero sus usuarios probablemente no harán esto. Probablemente...
@Gordon: no busqué los metacaracteres de Shell, pero tenía la intención de hacerlo. Gracias por hablarme de la campana... ¿Por qué demonios está \bahí, pero no \t? Ingenuamente, pensé \bque era el tabulador o el retroceso. Entendí un poco el retroceso, pero la campana no tiene sentido para mí.
@Gordon: aparentemente, la página de Bash usa una suposición incorrecta para OS X. En OS X bajo HFS +, los nombres de archivo pueden contener \0; consulte Mac OS X Lion: ¿Qué son los caracteres no válidos para un nombre de archivo? en Superusuario. Un líder \0es probablemente una buena manera para que un virus se oculte...

findpuede manejar espacios en los nombres de archivo. Usted for loopsestá causando los problemas. Puedes poner tu lógica en múltiples findcomandos. No todos los directorios en la carpeta de inicio de un usuario deben ser accesibles solo para ellos mismos. Público y Sitios vienen a la mente. Puede corregirlos por separado en otro comando de búsqueda.

find /Users/* ! -path '/Users/Shared*'  -type d ! \( -name Public -o -name Sites \) -exec chmod 0700 {} +

El comando excluye el directorio compartido junto con los directorios público y de sitios y luego cambia los permisos en los directorios restantes.

Puede usar +en sus finddeclaraciones para que findactúe como si fuera xargs.

find /Users/Shared -type d -exec chmod 0777 {} +

Suponiendo que los archivos ejecutables en la carpeta de inicio de un usuario son paquetes de aplicaciones, entonces podría hacer esto.

find /Users/* ! -path '/Users/Shared*' type f ! perm -755 -exec chmod 0600 {} +
Gracias fd0. "find puede manejar espacios en los nombres de archivo. Sus bucles for están causando los problemas". - Vale, bien, te confío en tu palabra. ¿Ahora que? Realmente no me dices cómo solucionarlo. (No se preocupe por Sharedo $HOME/Public. Eso es la guinda del pastel. La principal preocupación son los permisos en los directorios de inicio).

La solución de mtklr (y Patrix) funcionará, pero creo que es más simple usar un while readbucle cuando se trata de una lista de archivos de find ... -print0:

find /Users '!' -path '/Users/Shared*' -type f -print0 |
    while IFS= read -d '' -r file; do
        if file "$file" | grep -iq ": .*executable"; then
            chmod 700 "$file"
        else
            chmod 600 "$file"
        fi
    done

read -d ''utiliza valores nulos como delimitador. También solía -revitar que intentara analizar los escapes, y IFS=para evitar que recortara los espacios en blanco al final del nombre del archivo (y dado que es un prefijo del readcomando, solo se aplica a ese y no tiene que volver a configurarlo después).

Por cierto, también usé if ... | grep -q ...: el -qindica grepque no se genere nada, pero su estado de salida solo tendrá éxito si encontró al menos una coincidencia. Y dado que ifsolo prueba el estado de salida del comando en él, eso es todo lo que se necesita.

Por cierto, esto se basa principalmente en BashFAQ # 20: ¿Cómo puedo encontrar y manejar de manera segura los nombres de archivos que contienen líneas nuevas, espacios o ambos? . Recomiendo darle un vistazo...