Есть несколько реальных способов сделать это.
Если вы хотите придерживаться своей оригинальной версии, это можно сделать следующим образом:
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
with -print0
будет использовать null
(aka \0
) в качестве терминатора для каждого имени файла и, в отличие от новой строки, null не является допустимым символом в имени файла.
Преимущество этого перед почти эквивалентной версией
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"