有几种可行的方法可以完成此任务。
如果您想紧贴原始版本,可以这样进行:
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"