Есть несколько способов сделать это.
Если вы хотите придерживаться своей исходной версии, это можно сделать следующим образом:
getlist() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: %s\n' "$file"
done
}
Это все равно не удастся, если в именах файлов есть буквальные символы новой строки, но пробелы не будут разбиватьсяэто.
Однако, возиться с IFS не нужно.Вот мой предпочтительный способ сделать это:
getlist() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
Если вы найдете синтаксис < <(command)
незнакомым, вам следует прочитать о замене процесса .Преимущество этого перед for file in $(find ...)
в том, что файлы с пробелами, символами новой строки и другими символами обрабатываются правильно.Это работает, потому что find
с -print0
будет использовать null
(он же \0
) в качестве терминатора для каждого имени файла и, в отличие от новой строки, ноль не является допустимым символом в имени файла.
Преимущество этого перед почти эквивалентной версией
getlist() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done
}
В том, что любое присвоение переменной в теле цикла while сохраняется.То есть, если вы перенаправляете на while
, как указано выше, то тело while
находится в подоболочке, которая может не соответствовать вашему желанию.
Преимущество версии подстановки процесса над find ... -print0 | xargs -0
заключается в том, чтоминимальный: версия xargs
подходит, если все, что вам нужно, это напечатать строку или выполнить одну операцию над файлом, но если вам нужно выполнить несколько шагов, версия цикла будет проще.
РЕДАКТИРОВАТЬ : Вот хороший тестовый скрипт, чтобы вы могли понять разницу между различными попытками решения этой проблемы
#!/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"