gre*_*eth 185 linux bash shell
我想迭代一个文件列表.这个列表是find命令的结果,所以我想出了:
getlist() {
for f in $(find . -iname "foo*")
do
echo "File found: $f"
# do something useful
done
}
Run Code Online (Sandbox Code Playgroud)
没关系,除非文件名中有空格:
$ ls
foo_bar_baz.txt
foo bar baz.txt
$ getlist
File found: foo_bar_baz.txt
File found: foo
File found: bar
File found: baz.txt
Run Code Online (Sandbox Code Playgroud)
我该怎么做才能避免空格分裂?
mar*_*ton 241
您可以使用基于行的迭代替换基于单词的迭代:
find . -iname "foo*" | while read f
do
# ... loop body
done
Run Code Online (Sandbox Code Playgroud)
Sor*_*gal 149
有几种可行的方法可以实现这一目标.
如果你想紧贴原始版本,可以这样做:
getlist() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: %s\n' "$file"
done
}
Run Code Online (Sandbox Code Playgroud)
如果文件名中包含文字换行符,则仍然会失败,但空格不会破坏它.
但是,没有必要弄乱IFS.这是我首选的方法:
getlist() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
Run Code Online (Sandbox Code Playgroud)
如果您发现< <(command)语法不熟悉,您应该阅读有关进程替换的内容.这样做的好处for file in $(find ...)是可以正确处理带有空格,换行符和其他字符的文件.这是有效的,因为findwith -print0将使用null(aka \0)作为每个文件名的终止符,并且与换行符不同,null不是文件名中的合法字符.
相比于几乎等同的版本,这样做的优点
getlist() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done
}
Run Code Online (Sandbox Code Playgroud)
是否保留了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"
Run Code Online (Sandbox Code Playgroud)
mar*_*ing 30
还有一个非常简单的解决方案:依赖bash globbing
$ mkdir test
$ cd test
$ touch "stupid file1"
$ touch "stupid file2"
$ touch "stupid file 3"
$ ls
stupid file 3 stupid file1 stupid file2
$ for file in *; do echo "file: '${file}'"; done
file: 'stupid file 3'
file: 'stupid file1'
file: 'stupid file2'
Run Code Online (Sandbox Code Playgroud)
请注意,我不确定这种行为是默认行为,但我没有在我的shopt中看到任何特殊设置,所以我会说它应该是"安全的"(在osx和ubuntu上测试).
Kar*_*ath 13
find . -iname "foo*" -print0 | xargs -L1 -0 echo "File found:"
Run Code Online (Sandbox Code Playgroud)
Tor*_*orp 11
find . -name "fo*" -print0 | xargs -0 ls -l
Run Code Online (Sandbox Code Playgroud)
见man xargs.
由于您没有使用任何其他类型的过滤find,因此从bash4.0开始可以使用以下内容:
shopt -s globstar
getlist() {
for f in **/foo*
do
echo "File found: $f"
# do something useful
done
}
Run Code Online (Sandbox Code Playgroud)
在**/将匹配零个或多个目录,因此完整的模式将匹配foo*在当前目录或任何子目录.
| 归档时间: |
|
| 查看次数: |
90929 次 |
| 最近记录: |