Istnieje kilka wykonalnych sposobów, aby tego dokonać.
Jeśli chciał ściśle trzymać się pierwotnej wersji można to zrobić w ten sposób:
getlist() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: %s\n' "$file"
done
}
ten będzie nadal się niepowodzeniem, jeśli nazwy plików mają dosłowne znaki nowej linii w nich, ale obowiązuje go nie złamie.
Jednak zakłócanie komunikacji z IFS nie jest konieczne. Oto mój preferowany sposób to zrobić:
getlist() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
Jeśli okaże składnia < <(command)
zaznajomiony powinieneś przeczytać o process substitution. Zaletą tego ponad for file in $(find ...)
jest poprawne przetwarzanie plików ze spacjami, znakami nowej linii i innymi znakami. Działa to, ponieważ find
z -print0
użyje null
(alias \0
) jako terminatora dla każdej nazwy pliku i, w przeciwieństwie do nowej linii, null nie jest prawnym znakiem w nazwie pliku.
Zaletą tego na niemal równoważnej wersji
getlist() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done
}
jest, że każda zmienna przydzieleniem ciała pętli jest zachowana. To znaczy, jeśli podłączysz się do while
jak wyżej, korpus while
znajduje się w podpowłoce, która może nie być tym, czego potrzebujesz.
Zaletą wersji zastępującej proces nad jest minimalna: Wersja xargs
jest w porządku, jeśli wystarczy wydrukować linię lub wykonać pojedynczą operację na pliku, ale jeśli trzeba wykonać wiele kroków, wersja pętli jest łatwiej.
EDIT: Oto miły skrypt testowy, dzięki czemu można uzyskać wyobrażenie o różnicy pomiędzy różnymi próbami rozwiązania tego problemu
#!/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"
Jest to bardzo czysty. I sprawia, że czuję się lepiej niż zmiana IFS w połączeniu z pętlą for. – Derrick
Spowoduje to podzielenie ścieżki pliku, która zawiera \ n. OK, te nie powinny być w pobliżu, ale można je utworzyć: 'touch" $ (printf "foo \ nbar") "' –
@OllieSaunders: zawsze coś jest! –