![使用 find -exec command {} + 调用多少次命令](https://linux22.com/image/180225/%E4%BD%BF%E7%94%A8%20find%20-exec%20command%20%7B%7D%20%2B%20%E8%B0%83%E7%94%A8%E5%A4%9A%E5%B0%91%E6%AC%A1%E5%91%BD%E4%BB%A4.png)
查找联机帮助页状态:
-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
代替cmmnd
inside 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
为/////////a
or so,其他测试仍然会执行。您不应该忽略它们,因为它们-exec
首先决定路径名的路径。但如果测试确实或改变了某些东西,那么在没有这些的情况下执行它们可能是cmmnd
错误的。例如,您可能想要使用 删除文件
-delete -exec cmmnd … {} +
,其中cmmnd
会生成有关已删除文件的报告。在这种情况下使用/////////a
将删除文件没有生成任何报告。所以行动之前要三思。确保测试/操作/除了
-exec /////////a … {} +
不向标准输出打印任何内容之外的任何内容。或者让/a
使用其他渠道。即使没有,处理给定的目录树并执行(其他)测试也可能需要一段时间
cmmnd
。
答案4
嗯,标准文本说:
任何两个或多个路径名集合的大小均应受到限制,以便实用程序的执行不会导致超出系统的 {ARG_MAX} 限制。
因此它不应该构建太大而无法执行的参数列表。这会破坏这样的功能的意义。
它到底执行了多少次调用,取决于实现,并且您可能不应该太关心。该标准确实承诺同一-exec
子句的调用不会重叠,如果您执行具有外部状态的内容,这可能与正确性相关。
但是,在 Linux 上,命令行参数的实际最大大小基于堆栈大小,并且可以使用 间接更改ulimit -s
。看起来与eg 不同的是xargs
,find
我的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 -s
8192,因此您不太可能遇到该问题,除非在非常受限的系统上。