find . -exec cmd {} +
find . -print0 | xargs -0 cmd
两者都是对 find 找到的文件运行命令的可靠方法。
哪个是首选?哪个更便携、可靠、高效、多功能,为什么?
答案1
长话短说
没有明显的赢家。我的建议是使用:
find . -exec cmd {} +
只要它就足够了,因为它更便携,使用更少的资源并且问题更少,并且具有以下之一:
xargs -r0 -other-options -a <(find ... -print0 | ...) cmd
find . -print0 | ... | xargs -0 -other-options cmd
当您需要其他工具的附加功能xargs
或对其他工具的输出进行后处理 时find
,并且您知道您所在的系统支持这些非标准选项,并且这些限制不适用和/或可以忽略。
历史
find
已经有了-exec cmd {} ';'
,这种变体对
cmd
每个文件运行一次调用,并且还充当条件谓词,因为它在 70 年代中期的 Unix V5 中使用当前接口重新实现,但这种-exec cmd {} +
形式将多个文件传递给cmd
,尽可能多,来得很晚。它由 David Korn 编写,并于 1988 年在 System V Release 4 中首次发布(请参阅 参考资料lynx news://news.gmane.io/gmane.comp.standards.posix.austin.general/2192
),但直到 SVR4.2(1992 年)才记录下来。
这只是添加到 2001 版 POSIX 标准一些实现find
后来才添加它(4.2.12,2005年GNU
find
,2002年FreeBSD,2006年NetBSD,2015年busybox)
xargs
它本身来自 70 年代末的 PWB Unix。它曾经(并且仍然)有一个非常糟糕的界面,具有奇怪和不必要的功能和限制,理解一种独特的引用形式(尽管与尚未幸存的 PWB Unix shell 所理解的相当接近)。虽然它的目的是处理 的输出find
,但它不能可靠地做到这一点。
-0
1990 年, GNU 中添加了一个选项xargs
,同时又添加了一个新-print0
选项。
find
可以肯定的是,GNU作者在添加该选项时find
并不知道 SysV 。之后,-exec {} +
一些
-0
// --null
/选项已逐渐添加到其他 GNU 实用程序中,以处理 NUL 分隔的-z
问题--zero
交换可以携带任意文件路径和更一般的任意 C 字符串或命令行参数的格式。
-d
允许xargs
任何单字节记录定界符的选项,使其变得-0
多余,因为它与后来添加到 GNU xargs 中的相同-d '\0'
(在 2005 年末发布的 4.2.26 中),但迄今为止,据我所知,仍然只支持由 GNU 提供xargs
。
没有这些-0
或-d
(和-r
,见下文),xargs
几乎无法使用(可靠)。
-print0
此后, /已被添加到其他一些实现中,甚至在一些商业 SysV 派生的 Unix 上,例如 Solaris 11。 shell的内置命令-0
也支持它。find
bosh
它们不是标准的,但是在 POSIX 标准的下一版本中可能会变成这样(以及实用程序-d ''
的选项)read
。
-exec cmd {} +超过xargs -0 cmd
-exec cmd {} +
是标准的,现在相当便携。它的支持在 busybox 中仍然是可选的,因此您可能会遇到不可用的基于 Linux 的嵌入式系统。-ok cmd {} +
在执行之前提示用户的变体既不cmd
标准也不可移植(也不方便,因为命令行可能会变得很大)。
-print0
/xargs -0
不是标准的,但它现在常见于 BSD 和基于 Linux 的系统(包括 GNU、busybox' 和 toybox')的find
/实现中。xargs
仍然不支持在 AIX 上也不是 HP/UX。
在 GNU 系统之外,仍然很少找到支持 NUL 分隔记录的其他标准实用程序( sort
、sed
、cut
等)的其他实现。awk
find
可以-print0
用 来标准实现-exec printf '%s\0' {} +
,但是没有xargs -0
or sort
/ sed
/ grep
... -z 的标准等效项,更一般地说,NUL 不能由 POSIX 文本实用程序处理(一般也不能由文件路径处理,因为它们不能保证是文本)。
除了在某些 BSD 上之外,如果没有找到通常不需要的文件,find . -print0 | xargs -0 cmd
仍然会在没有参数的情况下运行一次。 cmd
GNU 实现xargs
添加了一个-r
选项来避免这种情况,但它不像-0
.
在 中find . -exec cmd {} +
,cmd
继承了find
的 stdin,因此cmd
如果该命令例如从终端启动,仍然能够与用户交互。
而在 中find . -print0 | xargs -r0 cmd
,根据xargs
实现的不同,cmd
的 stdin 将是 /dev/null (就像 GNU 一样xargs
),或者更糟的是继承xargs
' stdin,这是来自 的管道find
,所以如果它从它的 stdin 读取,它将造成严重破坏。通过 GNU 实现xargs
,可以使用-a
和进程替换来解决这个问题:
xargs -r0a <(find . -print0) cmd # Korn syntax
xargs -r0a <{find . -print0} cmd # rc syntax
xargs -r0a /dev/fd/3 3<(find . -print0) cmd # yash syntax
xargs -r0a (find . -print0|psub) cmd # fish syntax (not parallel though)
但这样的便携性要差很多。
在可靠性方面, find . -print0 | xargs -0 cmd
如果find
崩溃或过早被终止(例如因为它已达到资源限制),则可能会产生严重的后果。这是因为find
将其输出写入不保证以 NUL 分隔符结尾的块中(例如, with find /var/tmp -name '*.tmp'
,块可能以 结尾/var
),并且仍将为非分隔记录xargs
提供参数。cmd
例如,在我们的示例中,如果在输出以.结尾的块后被杀死,则可以使用 as 参数调用cmd
(如rm -rf
) 。/var
find
/var
这个问题不影响-exec cmd {} +
。
使用 时find . -exec cmd {} +
,退出状态反映find
和cmd
失败,而使用 时find | xargs
,在大多数 shell 中您只能获得 的退出状态,xargs
因此可能会错过并非所有文件都能找到的事实。许多 shell 都有一个pipefail
命令来缓解这种情况,另请参阅 zsh$pipestatus
或 bash $PIPESTATUS
,它们最终提供了更大的灵活性。
(非标准)-execdir cmd -- {} +
(注意不向文件名添加前缀的实现--
所需的)变体可以解决.find
./
-exec cmd {} +
xargs
xargs
在性能方面,find | xargs
意味着更多的工作(至少一个额外的进程,以及通过管道推送该数据),并且,因为其中一些最终是并行完成的(find
并且xargs
同时运行),所以最终可能会增加争用,因为两者find
并cmd
竞争 I/O 访问,因此可能会使用更多的总体资源。由于这种并行性,在某些情况下,它最终可能会更快地执行任务,因为find
可以继续搜索更多文件,同时cmd
忙于执行前一批的一些 CPU 密集型任务(至少直到管道和find
内部输出)缓冲区都已满)。
使用find . -exec cmd {} +
,可以更轻松地cmd
中止整个搜索。例如,与:
find . -exec sh -c 'if some-condition; then kill -s PIPE "$PPID"; exit 1; fi' sh {} +
使用find . -print0 | xargs -0 cmd
,cmd
可以执行exit 255
abort xargs
,但find
之后不会退出,直到它尝试将下一个块写入管道。
xargs -0 cmd 超过 -exec cmd {} +
这些观点的主要论点是,从一般意义上来说,它更通用、更灵活、更通用。
find
的-print0
输出是文件列表的可后处理表示形式,可以被任何东西使用,而不仅仅是xargs -0
.例如,您可以这样做:
find . -print0 |
grep -z foo |
sort -z
并且仍然获得可后处理、过滤和排序的文件路径列表。
同样xargs -0
可以用在那些 NUL 分隔的列表上,无论它们来自输出find
还是其他任何内容,无论表示文件路径还是其他任何内容。
在这方面,find . -exec cmd {} +
仅适用于狭窄的特殊用例(即使它是最常见的用例之一)。
对于xargs
,您可以使用-n
或-s
选项来限制传递给 的参数数量cmd
。对于 GNU xargs
,另请参阅-P
并行运行多个实例的选项cmd
,或xargs -0 -J {} mv {} /dest/
某些 BSD 的选项,以允许在文件列表后添加额外参数。
您可以将 的输出保存find . -print0
到文件中并稍后使用 处理它(例如,仅在find
完全成功的情况下)xargs -0 cmd < file
,避免 的输出cmd
干扰结果find
,包括(使用 GNU xargs
):
xargs -r0a =(find . -print0) cmd # zsh
xargs -r0a (find . -print0|psub -f) cmd # fish
没有与xargs
的exit 255
特殊处理等效的东西-exec cmd {} +
(尽管请参阅上面的 about kill "$PPID"
)。
使用find | xargs
,您可以更轻松地在不同的语言环境或更普遍的不同环境中运行find
和xargs cmd
(包括变量,限制,umask......)
例如,通常需要find
在 C 语言环境中运行来解决非文本文件名的问题,但通常仍希望cmd
在用户的语言环境中运行。
LC_ALL=C find . -exec cmd {} +
在 C 语言环境中运行 和find
。cmd
和
LC_ALL=C find . -exec env -u LC_ALL cmd {} +
首先是非标准的,但cmd
如果LC_ALL
事先定义,也可能无法恢复原始语言环境。
LC_ALL=C find . -print0 | xargs -r0 cmd
find
仅将语言环境更改为 C。
作为一个特殊情况:
find . -exec sudo cmd {} +
通常无法避免 args+env 大小的限制,因为sudo
设置SUDO_COMMAND
环境变量最终会重复参数列表。
find . -print0 | sudo xargs -r0 cmd
没有问题,因为$SUDO_COMMAND
在这种情况下仅包含xargs -0 cmd
(不要使用find . -print0 | xargs -r0 sudo cmd
)。
也可以看看:
sudo find . -print0 | xargs -r0 cmd
其中文件列表由 找到root
,但cmd
以原始用户身份运行。或者
find . -print0 | (USERNAME=some-user; xargs -r0 cmd)
在像 zsh 这样的 shell 中,它内置支持更改 (e)uid、(e)gids。
find . -print0 | xargs -r0 -- "${cmd[@]}"
$cmd
无论数组包含什么,都可以工作,而
find . -exec "${cmd[@]}" {} +
如果数组的任何元素$cmd
是或其中;
有连续元素,则失败。{}
+