寻找 。 -打印0 | xargs -0 cmd 与 find 。 -exec cmd {} +

寻找 。 -打印0 | xargs -0 cmd 与 find 。 -exec cmd {} +
  • 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,但它不能可靠地做到这一点。

-01990 年, 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也支持它。findbosh

它们不是标准的,但是在 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 分隔记录的其他标准实用程序( sortsedcut等)的其他实现。awk

find可以-print0用 来标准实现-exec printf '%s\0' {} +,但是没有xargs -0or sort/ sed/ grep... -z 的标准等效项,更一般地说,NUL 不能由 POSIX 文本实用程序处理(一般也不能由文件路径处理,因为它们不能保证是文本)。

除了在某些 BSD 上之外,如果没有找到通常不需要的文件,find . -print0 | xargs -0 cmd仍然会在没有参数的情况下运行一次。 cmdGNU 实现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) 。/varfind/var

这个问题不影响-exec cmd {} +

使用 时find . -exec cmd {} +,退出状态反映findcmd失败,而使用 时find | xargs,在大多数 shell 中您只能获得 的退出状态,xargs因此可能会错过并非所有文件都能找到的事实。许多 shell 都有一个pipefail命令来缓解这种情况,另请参阅 zsh$pipestatus或 bash $PIPESTATUS,它们最终提供了更大的灵活性。

(非标准)-execdir cmd -- {} +(注意不向文件名添加前缀的实现--所需的)变体可以解决.find./-exec cmd {} +xargsxargs

在性能方面,find | xargs意味着更多的工作(至少一个额外的进程,以及通过管道推送该数据),并且,因为其中一些最终是并行完成的(find并且xargs同时运行),所以最终可能会增加争用,因为两者findcmd竞争 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 cmdcmd可以执行exit 255abort 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

没有与xargsexit 255特殊处理等效的东西-exec cmd {} +(尽管请参阅上面的 about kill "$PPID")。

使用find | xargs,您可以更轻松地在不同的语言环境或更普遍的不同环境中运行findxargs cmd(包括变量,限制,umask......)

例如,通常需要find在 C 语言环境中运行来解决非文本文件名的问题,但通常仍希望cmd在用户的语言环境中运行。

LC_ALL=C find . -exec cmd {} +

在 C 语言环境中运行 和findcmd

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是或其中;有连续元素,则失败。{}+

相关内容