Hay varias formas viables para lograr esto.
Si quisieras apegarte a tu versión original, podrías hacerlo de esta manera:
getlist() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: %s\n' "$file"
done
}
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:
getlist() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
Si la < <(command)
sintaxis le resulta desconocida, debe leer sobre la sustitución de procesos . La ventaja de esto for file in $(find ...)
es que los archivos con espacios, líneas nuevas y otros caracteres se manejan correctamente. Esto funciona porque find
la -print0
va a utilizar un null
(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
getlist() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done
}
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 del while
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: la xargs
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
#!/usr/bin/env bash
dir=/tmp/getlist.test/
mkdir -p "$dir"
cd "$dir"
touch 'file not starting foo' foo foobar barfoo 'foo with spaces'\
'foo with'$'\n'newline 'foo with trailing whitespace '
# while with process substitution, null terminated, empty IFS
getlist0() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
# while with process substitution, null terminated, default IFS
getlist1() {
while read -d $'\0' -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
# pipe to while, newline terminated
getlist2() {
find . -iname 'foo*' | while read -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# pipe to while, null terminated
getlist3() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# for loop over subshell results, newline terminated, default IFS
getlist4() {
for file in "$(find . -iname 'foo*')" ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# for loop over subshell results, newline terminated, newline IFS
getlist5() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# see how they run
for n in {0..5} ; do
printf '\n\ngetlist%d:\n' $n
eval getlist$n
done
rm -rf "$dir"