当迭代某些文件名时,双引号参数不会阻止文件名被破坏:
$ mkdir temp
$ cd temp/
$ touch a\ b
$ for f in $(find .) ; do echo "$f" ; done
.
./a
b
答案1
该指令for f in $(find .)
将元素列表拆分为按空格进行迭代。在现代 shell 中,比如 zsh,扩展标志可用于。
for f in "${(@f)$(find .)}" ; do echo "$f" ; done
留下来bash
,我们可以解决这个问题就像另一个问题中提到的临时修改输入分隔符(用于分割元素的字符),同时将结果分配find
给变量。暂时禁用 gobbing 可以防止通配符的扩展,例如*
其中的通配符。
set -f # Disable globbing.
IFS=$(echo -e '\0') files=( $(find . -print0) ) # Read newline separated files into an array.
for f in ${files[@]}; do echo $f; done
set +f # Reenable globbing.
但这只是转移了问题,现在当换行符位于任何文件名中时它都不起作用。可能所有文件系统中都禁止一件事,那就是空字符。但是bash中的变量可能不保存这个,所以我们不能将它分配给IFS
.
或者,可以使用 xargs 将路径传递到find
另一个程序。使用-print0
命令 tofind
和-0
开关 to xargs
,它们使用空字符作为分隔符。添加-n1
到xargs
调用一次可处理一个文件名。
find . -print0 | xargs -0 -n1 echo
答案2
您用双引号引用了参数中的变量替换echo
,但没有对循环的迭代列表中的命令替换进行双引号引用for
。这就是空白和其他肮脏问题发生分裂的地方。
没有完全可靠的方法来解析 的输出find
,因为您无法判断换行符是文件名的一部分还是分隔符的一部分。
find
如果您可以假设您的文件名不包含换行符,那么您可以通过将字段分隔符限制为仅换行符(以保护空格和制表符)并关闭通配符(以保护\[?*
)来安排分割输出。
IFS='
'; set +f
for f in $(find .); do
unset IFS; set +f
echo "$f"
done
unset IFS; set +f
不过,还有更好的方法可以做到这一点。可靠使用的最简单方法find
是让它执行命令,而不是解析其输出。这也可能会更快,因为文件很有可能在其元数据仍在缓存中时得到处理。
find . -exec echo {} \;
如果您需要 shell 命令,请显式调用 shell。注意引用 — 将文件名作为参数传递给 shell,不要尝试在 shell 命令中插入它。
find . -exec sh -c 'echo "$0"' {} \;
您可以对 shell 调用进行分组并循环遍历一堆文件,而不是为每个文件调用一个 shell 实例。该find
命令进行聚合并确保不超过命令行长度限制。不要忘记传递一个虚拟参数,$0
以便文件$1
等等。
find . -exec sh -c 'for x; do echo "$x"; done' _ {} +
如果您使用 ksh93、bash 或 zsh,则可以使用它们的递归通配符功能:**/PATTERN
在目录及其子目录中递归搜索通配符模式。在ksh93中,需要set -o globstar
先运行。在 bash 中,您需要shopt -s globstar
首先运行,并注意它**
也会在目录的符号链接内递归。
for f in **/*; do
echo "$f"
done
Zsh也有全局限定符包含 的大多数用途find
。其他的shell没有这样的东西。