OSX Bash for loop - ¿problemas con espacios en los nombres de las carpetas?

7

Me estoy confundiendo un poco sobre cómo manejar los espacios en los nombres de ruta cuando se devuelven en un bucle for.

Justificación: estoy limpiando los permisos de las carpetas y archivos que copio desde Windows. La mayoría de los archivos terminan con -rwx------ o -rwxr-xr-x permisos, así que me gusta hacer " chmod -x * " y luego " chmod u+x <folders> ", así que estoy intentando lo siguiente:

$ alias getdirs='find . -maxdepth 1 -mindepth 1 -type d | cut -c 3-'
$ for i in $(getdirs); do chmod u+x $i; done

que funciona bien, siempre que los directorios no tengan un espacio en el nombre.

He intentado diferentes permutaciones de chmod u+x "$i" , chmod u+x '$i' y similares para obtener el comportamiento que quería, pero fue en vano.

¿Cómo mejorar mi código de bash, que funciona con nombres de carpetas que contienen espacio?

El propósito de esto es poder eliminar el bit "exec" de los archivos simples (de ahí la parte chmod -x * ) pero luego restaurarlos en los directorios para permitir que entren en ellos ( %código%). A partir de los comentarios y respuestas hasta ahora, estoy pensando que probablemente será más fácil hacerlo con el conjuro de "encontrar" adecuado

    
pregunta JJarava 12.12.2013 - 13:25

5 respuestas

10

Este tipo de cosas puede ser complicado en todos los shells de Unix debido a la forma en que el espacio actúa como un separador, ejecutar alias como parte de los scripts de shell hace las cosas aún más interesantes. Probablemente ejecutaría dos pasos de buscar para establecer primero los directorios en orden y luego los archivos:

find . -maxdepth 1 -mindepth 1 -type d -exec chmod u+x '{}' \;
find . -maxdepth 1 -mindepth 1 -type f -exec chmod u-x '{}' \;
    
respondido por el nohillside 12.12.2013 - 14:07
6

En general, la forma 'adecuada' de analizar la salida de find en un bucle bash es usar un bucle while read , en lugar de un bucle for . En bash, los bucles se dividen usando cualquier espacio en blanco (espacio, tabulador, nueva línea) de forma predeterminada. Esto se puede cambiar, pero es más fácil y (en mi opinión) más limpio usar read , que lee una línea a la vez de forma predeterminada .

find . -maxdepth 1 -mindepth 1 -type d | while read i; do chmod u+x "$i"; done

Tenga en cuenta que cité el "$i" allí; eso es igual de importante, ya que las variables de comillas impiden que el shell divida su contenido (es el mismo problema que for tiene, pero en el otro extremo). También tenga en cuenta que no puede usar comillas simples: '$i' devolvería un $i literal, en lugar del contenido de la variable.

Esto todavía se dividirá en directorios con nuevas líneas en sus nombres. Hay una solución que involucra a -print0 de find, pero solo he visto líneas nuevas en nombres de archivos creados específicamente para probar scripts. No sé si esto funciona con la versión de bash en OSX (tomada de wiki de greg ):

find . -maxdepth 1 -mindepth 1 -type d -print0 | while IFS= read -r -d '' i; do chmod u+x "$i"; done

Sin embargo, en este caso, es más fácil usar globos: un globo que termina en / se expandirá solo a los directorios, por lo que simplemente podría

chmod u+x */

(Esto funcionará perfectamente bien con espacios, nuevas líneas, cualquier cosa). Más generalmente, para recorrer todos los directorios:

for f in */; do stuff with "$f"; done

Lamentablemente, no hay forma de seleccionar archivos solo con globs en ninguna versión de bash (esta es una de las razones por las que prefiero zsh, donde los globs son lo suficientemente poderosos para que nunca tengas que molestarte en encontrar).

    
respondido por el evilsoup 12.12.2013 - 19:29
3

Tengo dos sugerencias:

  1. Use sed para poner comillas alrededor de todos los nombres de directorio.
  2. Canalice a xargs con el argumento -L 1 . Esto ejecutará un comando en cada línea de entrada estándar, obviando el bucle for .

Prueba esta canalización:

find . -maxdepth 1 -mindepth 1 -type d | cut -c 3- | sed 's/.*/"&"/' | xargs -L 1 chmod u+x

    
respondido por el Flavin 12.12.2013 - 15:54
2

El meollo del problema es que la división de palabras ocurre para sustitución de comandos , de modo que los nombres con Los espacios en ellos son indistinguibles de los nombres separados por completo. Globbing no tiene esta dificultad, por lo que si hay una manera de identificar directorios con una glob y evitar la Sustitución del mando por completo, estás en casa libre. Y ahí está:

chmod -x *
chmod u+x */

Pero incluso esto es demasiado trabajo, porque chmod tiene el indicador simbólico X (capital X), que aplica el bit ejecutable solo a los directorios y archivos que ya son ejecutables. Así que realmente puedes hacerlo:

chmod -x *
chmod u+X *
    
respondido por el kojiro 12.12.2013 - 20:58
1

O bien su alias solo está extrayendo el primer bit antes del espacio, o su bucle for solo está leyendo el primer bit

Puede probar esto agregando algunos comentarios de prueba a sus comandos. He limitado el alias para que solo tire del último directorio encontrado para aislar las iteraciones a 1, imprimí lo que el alias cree que está recibiendo y luego hizo eco de la variable contenidos para asegurar que coinciden:

jaravj$ alias getdirs='find . -maxdepth 1 -mindepth 1 -type d | tail -1|  cut -c 3-'
jaravj$ getdirs
jaravj$ for i in $(getdirs); do echo "Filename is "$i; chmod u+x $i; done

EDITAR: se modificó do; echo ... a do echo ... porque impedía que la línea se ejecutara

    
respondido por el stuffe 12.12.2013 - 13:35

Lea otras preguntas en las etiquetas