Il existe également une solution très simple :compter sur bash globbing
$ mkdir test
$ cd test
$ touch "stupid file1"
$ touch "stupid file2"
$ touch "stupid file 3"
$ ls
stupid file 3 stupid file1 stupid file2
$ for file in *; do echo "file: '${file}'"; done
file: 'stupid file 3'
file: 'stupid file1'
file: 'stupid file2'
Notez que je ne suis pas sûr que ce comportement soit celui par défaut, mais je ne vois aucun paramètre spécial dans ma boutique, donc j'irais dire qu'il devrait être "sûr" (testé sur osx et ubuntu).
Il existe plusieurs façons pratiques d'y parvenir.
Si vous souhaitez vous en tenir étroitement à votre version originale, vous pouvez le faire de la manière suivante :
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 retours à la ligne littéraux, mais les espaces ne le casseront pas.
Cependant, jouer avec IFS n'est pas nécessaire. Voici ma méthode préférée :
getlist() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
Si vous trouvez le < <(command)
syntaxe peu familière, vous devriez lire sur la substitution de processus. L'avantage de ceci sur 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
(alias \0
) comme caractère de fin 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 par rapport à la version quasi é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 dirigez vers while
comme ci-dessus puis le corps du while
est dans un sous-shell qui n'est peut-être pas ce que vous voulez.
L'avantage de la version de substitution de processus par rapport à find ... -print0 | xargs -0
est minimal :le xargs
La 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 simple.
MODIFIER :Voici un bon script de test pour que vous puissiez vous faire 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"
Vous pouvez remplacer l'itération basée sur les mots par une itération basée sur les lignes :
find . -iname "foo*" | while read f
do
# ... loop body
done