Існує кілька працездатних способів досягти цього.
Якщо ви хочете тісно дотримуватися оригінальної версії, це можна зробити так:
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
) в якості термінатора для кожного імені файлу, і, на відміну від нового рядка, 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
версія чудова, якщо все, що вам потрібно, це надрукувати рядок або виконати одну операцію над файлом, але якщо вам потрібно виконати кілька кроків, версія циклу простіше.
EDIT : Ось хороший тестовий сценарій, щоб ви могли зрозуміти різницю між різними спробами вирішення цієї проблеми
#!/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"