Quiero iterar sobre una lista de archivos. Esta lista es el resultado de un find
comando, así que se me ocurrió:
getlist() {
for f in $(find . -iname "foo*")
do
echo "File found: $f"
# do something useful
done
}
Está bien, excepto si un archivo tiene espacios en su nombre:
$ ls
foo_bar_baz.txt
foo bar baz.txt
$ getlist
File found: foo_bar_baz.txt
File found: foo
File found: bar
File found: baz.txt
¿Qué puedo hacer para evitar la división en espacios?
Respuestas:
Puede reemplazar la iteración basada en palabras con una basada en líneas:
fuente
touch "$(printf "foo\nbar")"
IFS= while read -r f
en su lugar.find
y un ciclo while.-exec
va a ser más limpio que un bucle explícito:find . -iname "foo*" -exec echo "File found: {}" \;
. Además, en muchos casos puede sustituir a la última\;
con la+
que poner un montón de archivos en el sistema.Hay varias formas viables para lograr esto.
Si quisieras apegarte a tu versión original, podrías hacerlo de esta manera:
Esto seguirá fallando si los nombres de archivo tienen nuevas líneas literales, pero los espacios no lo romperán.
Sin embargo, no es necesario jugar con IFS. Aquí está mi forma preferida de hacer esto:
Si la
< <(command)
sintaxis le resulta desconocida, debe leer sobre la sustitución de procesos . La ventaja de estofor file in $(find ...)
es que los archivos con espacios, líneas nuevas y otros caracteres se manejan correctamente. Esto funciona porquefind
la-print0
va a utilizar unnull
(alias\0
) como terminador para cada nombre de archivo y, a diferencia de nueva línea, null no es un carácter legal en un nombre de archivo.La ventaja de esto sobre la versión casi equivalente
Es que cualquier asignación variable en el cuerpo del ciclo while se conserva. Es decir, si se canaliza
while
como se indica anteriormente, el cuerpo delwhile
está en una subshell que puede no ser lo que desea.La ventaja de la versión de sustitución del proceso
find ... -print0 | xargs -0
es mínima: laxargs
versión está bien si todo lo que necesita es imprimir una línea o realizar una sola operación en el archivo, pero si necesita realizar varios pasos, la versión en bucle es más fácil.EDITAR : Aquí hay un buen script de prueba para que pueda tener una idea de la diferencia entre los diferentes intentos de resolver este problema
fuente
$IFS
la< <(cmd)
sintaxis. Todavía queda una cosa que oscurecen a mí, por qué el$
de$'\0'
? Muchas gracias.while IFS= read
... para manejar archivos que comienzan o terminan con espacios en blanco.bash
así que me sentí seguro usando funciones específicas de bash. La sustitución del proceso no es portátil a otros shells (es probable que sh no reciba una actualización tan significativa).IFS=$'\n'
confor
previene la división de palabras interna de la línea, pero aún así hace que las líneas resultantes estén sujetas a glob, por lo que este enfoque no es completamente robusto (a menos que también desactive el globbing primero). Si bienread -d $'\0'
funciona, es un poco engañoso, ya que sugiere que puede usar$'\0'
para crear NUL; no puede: a\0
en una cadena con comillas ANSI C termina efectivamente la cadena, por lo que-d $'\0'
efectivamente es lo mismo-d ''
.También hay una solución muy simple: confiar en bash globbing
Tenga en cuenta que no estoy seguro de que este comportamiento sea el predeterminado, pero no veo ninguna configuración especial en mi tienda, así que diría que debería ser "seguro" (probado en osx y ubuntu).
fuente
fuente
Ver
man xargs
.fuente
Como no está haciendo ningún otro tipo de filtrado
find
, puede usar lo siguiente a partir debash
4.0:El
**/
coincidirá con cero o más directorios, por lo que el patrón completo coincidiráfoo*
en el directorio actual o cualquier subdirectorio.fuente
Realmente me gustan los bucles y la iteración de matrices, así que creo que agregaré esta respuesta a la mezcla ...
También me gustó el estúpido ejemplo de archivo de marchelbling. :)
Dentro del directorio de prueba:
Esto agrega cada línea de listado de archivos en una matriz bash nombrada
arr
con cualquier nueva línea final eliminada.Digamos que queremos dar mejores nombres a estos archivos ...
$ {! arr [@]} se expande a 0 1 2, por lo que "$ {arr [$ i]}" es el elemento número i de la matriz. Las comillas alrededor de las variables son importantes para preservar los espacios.
El resultado son tres archivos renombrados:
fuente
find
tiene un-exec
argumento que recorre los resultados de búsqueda y ejecuta un comando arbitrario. Por ejemplo:Aquí
{}
representa los archivos encontrados, y envolverlo""
permite que el comando de shell resultante trate con espacios en el nombre del archivo.En muchos casos, puede reemplazar ese último
\;
(que inicia un nuevo comando) con un\+
, que colocará varios archivos en el mismo comando (aunque no necesariamente todos a la vez, consulteman find
para obtener más detalles).fuente
En algunos casos, aquí si solo necesita copiar o mover una lista de archivos, también puede canalizar esa lista a awk.
Importante
\"" "\"
alrededor del campo$0
(en resumen, sus archivos, una lista de líneas = un archivo).fuente
Ok, ¡mi primera publicación en Stack Overflow!
Aunque mis problemas con esto siempre han estado en csh, no bash, la solución que presento funcionará, estoy seguro, en ambos. El problema es con la interpretación del shell de los retornos "ls". Podemos eliminar "ls" del problema simplemente usando la expansión de shell del
*
comodín, pero esto da un error de "no coincidencia" si no hay archivos en la carpeta actual (o en la carpeta especificada), para solucionar esto simplemente ampliamos el expansión para incluir archivos de puntos por lo tanto:* .*
- esto siempre dará resultados desde los archivos. y ... siempre estará presente. Entonces, en csh podemos usar esta construcción ...si desea filtrar los archivos de puntos estándar, entonces eso es bastante fácil ...
El código en la primera publicación de este hilo se escribiría así:
¡Espero que esto ayude!
fuente
Otra solución para el trabajo ...
El objetivo era:
fuente