使用 find -exec command {} + 调用多少次命令

使用 find -exec command {} + 调用多少次命令

查找联机帮助页状态:

   -exec command {} +
          This variant of the -exec action runs the specified command on the selected files,
          but the command line is built by appending each selected file name at the end;
          the total number of invocations of the  command  will  be
          much  less than the number of matched files.

一直以为这会导致只find执行command一次。有没有办法知道命令被调用了多少次?

请注意,这一点很重要,因为正如我所想的那样,如果这只是一次,那么就存在构建太大而无法command处理的参数列表的危险;但如果 find 最终会将调用分开(有点类似于parallel),那么这种情况就会得到缓解。

答案1

使用的缓冲区取决于find版本,在我在这里提供的 SuSE 框中,大小似乎约为 256Kb。

因此,要计算“命令”被调用的次数,您需要知道每个找到的文件路径的长度,那么它将(大约)是所有路径长度的总和加上分隔空间加一,减去命令本身除以缓冲区大小。

例如,您发现 20,000 个文件,平均路径长度为 200 字节,即 4,020,000 字节,除以 256 Kb 等于 15.33,因此您需要大约 16 次调用。

考虑到不破坏两个连续调用之间的文件路径的需要,确切的计算会稍微复杂一些,但您会得到一个大概的数字。

这里对于一个线程(带有源代码),其大小据报道为 32Kb,并且被认为不必要地低(现在我想起来了,也许是我自己的find 使用系统限制。我没有实验过);coreutils的版本,据推断,似乎是四倍,即 128 Kb

答案2

该限制将取决于find(1)的缓冲区以及命令处理的内容(取决于内核)。除非最后一个百分比的性能至关重要,否则系统上的默认设置应该没问题。

如果您担心性能,请考虑所有的执行此操作的系统,以及措施瓶颈在哪里。有机会你会是非常对你的发现感到惊讶。 Bentley 在他精彩的《编写高效程序》(Prentice-Hall,1982 年)(遗憾的是早已绝版)中分享了几个仔细“优化”的故事,这些故事使本质上未使用的、有致命错误的代码“更快”或优化了一个程序的空闲循环。操作系统在测量后发现它占用了计算机的很大一部分时间。人们是臭名昭著地不善于猜测效率低下的地方。此外,在更高层次(系统架构、整体组织、算法和数据结构)上工作比在细节上工作更有价值。

答案3

初步说明:手册和您的问题用于command表示命令,但由于 POSIX 定义了一个字面名为的实用程序command,我的答案会用cmmnd


如果你想实际运行cmmnd(s)并只计算调用次数(要知道 find完成)然后创建一个包装器来执行您可以计数的操作(例如打印到 stderr、打印到日志文件、发出蜂鸣声)并最终运行cmmnd.例子:

#!/bin/sh
echo "invoking cmmnd" >&2
cmmnd "$@"

然后使用 thewrapper代替cmmndinside find

注意创建时find会使用/absolute/path/to/wrapper不太长的命令;那么包装器将使用/absolute/path/to/cmmnd.如果后者更长,那么包含它的某些命令行可能会变得太长。所以这种方法并不像我们希望的那么简单。您可以通过find逐字提供附加斜杠(例如)来扩展前一个路径/absolute/path/to/////wrapper


现在我假设你想知道这个数字你决定跑步cmmnd。就像在这样的情况下,调用cmmnd两次是一件坏事(无论出于何种原因),并且您想确保find只运行一次。

cmmnd "$@"可以使用上面注释掉的包装器。以下是一些其他想法(尽管最终没有那么不同)。

假设您想这样做:

find . -exec cmmnd … {} +

(其中表示常量参数)。找出cmmnd真正的绝对路径是什么。例如,可以是/bin/cmmnd。然后运行这样的事情:

find . -exec /aaa/zzzzz … {} +

其中/aaa/zzzzz是一个不存在的命令,其名称的长度与 相同/bin/cmmnd。现在find将构建命令行,/aaa/zzzzz其长度与命令行的长度相同/bin/cmmnd。你会得到

find: '/aaa/zzzzz': No such file or directory

一次或多次。数一数即可得到您想要的数字。这个简单的方法:

find . -exec /aaa/zzzzz … {} + 2>&1 | wc -l

不是最好的,因为find可能打印例如permission denied它遇到的一些文件。但是,如果您创建/aaa/zzzzz一个有效的可执行文件,只打印一行(可以是空行),那么这应该可以工作:

find . -exec /aaa/zzzzz … {} + | wc -l

另一个改进是命名该工具/a(而不是/aaa/zzzzz),并将其称为/////a/////////////////a等,具体取决于您需要的长度。例子:

find . -exec /////////a … {} + | wc -l

为了完整起见,可能如下a所示:

#!/bin/sh
echo

它几乎就像我们的包装器没有cmmnd "$@",但它使用标准输出。

笔记:

  • 确切的字符数/并不重要。少数人的错误不会改变结果彻底地。如果您需要一个估计结果,你可以盲目使用///////////a左右,除非通往的路径cmmnd异常长。请注意,精确使用/a将为您提供下限。

  • 在实践中,您之前经常进行其他测试-exec cmmnd … {} +。如果替换cmmnd/////////aor so,其他测试仍然会执行。您不应该忽略它们,因为它们-exec首先决定路径名的路径。但如果测试确实或改变了某些东西,那么在没有这些的情况下执行它们可能是cmmnd错误的。

    例如,您可能想要使用 删除文件-delete -exec cmmnd … {} +,其中cmmnd会生成有关已删除文件的报告。在这种情况下使用/////////a将删除文件没有生成任何报告。所以行动之前要三思。

  • 确保测试/操作/除了-exec /////////a … {} +不向标准输出打印任何内容之外的任何内容。或者让/a使用其他渠道。

  • 即使没有,处理给定的目录树并执行(其他)测试也可能需要一段时间cmmnd

答案4

嗯,标准文本说:

任何两个或多个路径名集合的大小均应受到限制,以便实用程序的执行不会导致超出系统的 {ARG_MAX} 限制。

因此它不应该构建太大而无法执行的参数列表。这会破坏这样的功能的意义。

它到底执行了多少次调用,取决于实现,并且您可能不应该太关心。该标准确实承诺同一-exec子句的调用不会重叠,如果您执行具有外部状态的内容,这可能与正确性相关。

但是,在 Linux 上,命令行参数的实际最大大小基于堆栈大小,并且可以使用 间接更改ulimit -s。看起来与eg 不同的是xargsfind我的Debian 和Ubuntu 上的实际上并没有在运行时检查限制,因此理论上可能会遇到问题。

$ mkdir bar
$ touch bar/{00000..99999}
$ ulimit -Ss 512
$ getconf ARG_MAX
131072
$ find bar -type f -exec sh ./args.sh {} +
find: ‘sh’: Argument list too long
find: ‘sh’: Argument list too long
...

但是,默认值为ulimit -s8192,因此您不太可能遇到该问题,除非在非常受限的系统上。

相关内容