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

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

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

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

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

El propósito de esto es poder eliminar el bit "exec" de los archivos sin formato (de ahí la chmod -x *parte) pero luego restaurarlo en los directorios para permitir acceder a ellos ( chmod u+x <dirname>). De los comentarios y respuestas hasta ahora, creo que probablemente será más fácil hacerlo con el encantamiento adecuado de "encontrar".

¿Por qué está usando el corte? ¿No podría simplemente usar -exec?
Cada uno por su cuenta, encadenar una gran cantidad de comandos con tuberías puede actuar como una buena manera de proporcionar una especie de comentario basado en código de pasos lógicos, mientras que la mayoría de las cosas se pueden condensar en pasos mínimos dentro de comandos como findy sed, awketc., muchos menos experimentados los expertos en shell encuentran que las cadenas de comandos de estilo de diagrama de flujo son más legibles y comprensibles.
@Mark: quiero deshacerme de ./ al comienzo de la salida para obtener una salida "agradable", eso es todo
@stuffe, pero luego tiene que lidiar con este problema de cadena, por lo que encuentro que ambos son igualmente complejos. Mi sugerencia es renunciar al shell y usar un lenguaje más completo que no esté basado en texto, por ejemplo, python, perl, tcl, etc.
@Mark En realidad, ese es el "plan b". Estoy razonablemente cómodo con Python (es mi lenguaje de secuencias de comandos de elección para desarrollar utilidades) y desde que me mudé a Mac he desarrollado un poco más, pero estaba tratando de hacerlo "a la manera de Unix". De las otras respuestas, creo que "el camino a seguir" (aparte de un script de Python completo) es probablemente aprovechar "buscar". Lo miraré y te informaré
Dividir palabras no es tan complicado, y en realidad es más fácil entenderlo que tratar de evitarlo. Además, en algún momento inevitablemente querrá escribir un programa de Python que use argumentos de línea de comandos y esos argumentos estarán sujetos a división de palabras.

Respuestas (5)

Este tipo de cosas pueden ser complicadas 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 que las cosas sean aún más interesantes. Probablemente ejecutaría dos pasadas de find 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 '{}' \;
Y para dar respuesta a la necesidad inicial:find . -maxdepth 1 -mindepth 1 -type f -exec chmod u-x '{}' \;

En general, la forma 'adecuada' de analizar la salida de find en un bucle bash es usar un while readbucle, en lugar de un forbucle. En bash, los bucles for 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 de 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é "$i"allí, eso es igual de importante, porque citar variables evita que el shell divida su contenido (es el mismo problema que fortiene, pero en el otro extremo). También tenga en cuenta que no puede usar comillas simples: '$i'devolvería un literal $i, en lugar del contenido de la variable.

Esto aún fallará en los directorios con líneas nuevas en sus nombres. Hay una solución alternativa que involucra find's -print0, 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 (tomado de la 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 globs: un glob que termina en a /se expandirá solo a directorios, por lo que podría simplemente

chmod u+x */

(esto funcionará perfectamente bien con espacios, saltos de línea, cualquier cosa). De manera más general, para recorrer todos los directorios:

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

Desafortunadamente, 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 como para no tener que molestarse en encontrarlos).

Yo diría que analizar la salida de nuncafind es adecuado. No existe ningún escenario en el que analizar la salida de sea más efectivo que o , o, en circunstancias muy limitadas,find-exec-execdirfind … -print0 | xargs -0 …
@kojiro No estoy de acuerdo. Tu comentario es demasiado ingenuo. Para operaciones repetitivas en listas de resultados grandes o listas de búsqueda obtenidas de grandes sistemas de archivos que toman mucho tiempo para buscar, es ventajoso almacenar el resultado de buscar y procesarlo en rutinas separadas para que no tenga que repetir el comando de búsqueda o presionar. los límites de las secuencias de comandos de una sola línea. Y ese es solo uno de esos ejemplos en los que su argumento se desmorona. Hay mas.
@IanC. Entonces almacene en caché la salida de find … -print0. Además, sostengo que almacenar en caché una lista de nombres de archivo no desinfectados está superando los límites de las secuencias de comandos de una sola línea. El problema de invalidación de caché tampoco es insignificante.
Gracias por la sugerencia sobre el uso de un ciclo while; de ​​hecho, funciona mejor (es decir, funciona). El problema con el uso de la expansión global es que no funcionará con directorios "ocultos" (como .Testdir) mientras que FIND sí lo hará.
@JJarava Solo hazlo shopt -s dotglobpara habilitar la expansión global de nombre punteado.

Tengo dos sugerencias:

  1. Úselo sedpara poner comillas alrededor de todos los nombres de directorio.
  2. Pipe to xargswith argument -L 1. Esto ejecutará un comando en cada línea de stdin, obviando el forbucle.

Pruebe esta canalización:

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

En realidad, estoy buscando aprovechar find directamente para hacer el cambio de permisos directamente y evitar bucles y conductos complejos. Publicaré aquí si logro hacerlo.

El quid de la cuestión es que la división de palabras ocurre para la sustitución de comandos , por lo que los nombres con espacios en ellos son indistinguibles de los nombres separados por completo. Globbing no sufre de esta dificultad, por lo que si hay una manera de identificar directorios con un glob y evitar la sustitución de comandos por completo, está libre. Y ahí está:

chmod -x *
chmod u+x */

Pero incluso esto es demasiado trabajo, porque chmodtiene la Xbandera simbólica (X mayúscula), que aplica el bit ejecutable solo a directorios y archivos que ya son ejecutables. Así que realmente puedes hacer:

chmod -x *
chmod u+X *

O 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 en sus comandos, he limitado el alias para extraer solo el último directorio encontrado para aislar las iteraciones a 1, imprimí lo que el alias cree que está recibiendo y luego repetí el contenido de la variable para asegurar ellos 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: cambiado do; echo ...a do echo ...como estaba impidiendo que la línea se ejecutara

Intenté su sugerencia y la salida es la esperada: tengo una carpeta llamada "carpeta de datos ZZPRODUCT", por lo que la salida de FOR es: El nombre de archivo es ZZPRODUCT chmod: ZZPRODUCT: No existe tal archivo o directorio El nombre de archivo es data chmod: data: No existe tal archivo o directorio El nombre del archivo es la carpeta chmod: carpeta: No existe tal archivo o directorio
¡@JJarava, la salida NO ES la esperada si su carpeta tiene el nombre ZZPRODUCT data folder! Filename is ZZPRODUCT data folderSi fuera como se esperaba, la salida no habría sido Filename is ZZPRODUCTcomo usted indica. Stuffe ha encontrado el problema.
Si la salida del eco está truncada y la salida de getdirsno lo está, el problema está en el bucle thap, si getdirsestá truncado, el problema está en su alias. Habiendo dicho eso, me gusta la solución de @patrix, ¿la has probado?