Il existe plusieurs façons pratiques d'y parvenir.
Si vous vouliez vous en tenir à votre version originale, cela pourrait être fait de cette façon:
getlist() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: %s\n' "$file"
done
}
Cela échouera toujours si les noms de fichiers contiennent des sauts de ligne littéraux, mais les espaces ne le cassent pas.
Cependant, jouer avec IFS n'est pas nécessaire. Voici ma façon préférée de le faire:
getlist() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
Si vous trouvez la < <(command)
syntaxe peu familière, vous devriez lire à propos de la substitution de processus . L'avantage de ceci for file in $(find ...)
est que les fichiers avec des espaces, des retours à la ligne et d'autres caractères sont correctement gérés. Cela fonctionne car find
avec -print0
utilisera un null
(aka \0
) comme terminateur pour chaque nom de fichier et, contrairement à la nouvelle ligne, null n'est pas un caractère légal dans un nom de fichier.
L'avantage de cela par rapport à la version presque équivalente
getlist() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done
}
Est-ce que toute affectation de variable dans le corps de la boucle while est préservée. Autrement dit, si vous redirigez vers while
comme ci-dessus, le corps du while
est dans un sous-shell qui peut ne pas être ce que vous voulez.
L'avantage de la version de substitution de processus find ... -print0 | xargs -0
est minime: la xargs
version est correcte si tout ce dont vous avez besoin est d'imprimer une ligne ou d'effectuer une seule opération sur le fichier, mais si vous devez effectuer plusieurs étapes, la version en boucle est plus facile.
EDIT : Voici un joli script de test afin que vous puissiez avoir une idée de la différence entre les différentes tentatives de résolution de ce problème
#!/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"