侧边栏壁纸
博主头像
一笑痕

人生若只如初见,
是可喜亦或者是可悲?

  • 累计撰写 148 篇文章
  • 累计收到 7 条评论

-exec 分号每个文件起一次进程,加号一次装上三百个

2026-9-29 / 0 评论 / 4 阅读

-exec 分号每个文件起一次进程,加号一次装上三百个

300 个文件的目录,同一条 find 语句,只把末尾的分号换成加号:命令调用次数从 300 次降到 1 次,耗时从 603 毫秒降到 6 毫秒。少掉的不是文件处理的时间,是启动进程的时间。

第一组数字,同一个目录跑两遍:

== 1. 300 个文件,两种写法的调用次数与耗时 ==
  -exec ... 分号:命令调用 300 次,文件参数合计 300 个,退出码 0,耗时 603 ms
  -exec ... 加号:命令调用 1 次,文件参数合计 300 个,退出码 0,耗时 6 ms

两次的文件参数都是 300 个,处理量一样,差别只在命令被调用的次数上。300 次 fork 加 exec 换来 597 毫秒的差距,平均每次两次系统调用花掉约 2 毫秒。

加号不是「永远只调一次」

它的规则是按参数长度攒,攒到接近 ARG_MAX 就执行一批,剩下的接着攒。刚才是 300 个短文件名,一批装得下。换成 4000 个 240 字符的长名文件:

== 2. 4000 个长名文件,加号写法被切成几次 ==
  命令调用 3 次,每次带 1630, 1630, 740 个文件,合计 4000 个
  耗时 23 ms,退出码 0

这批文件路径合计 1284000 字节,本机 getconf ARG_MAX 是 1048576,所以被切成了三批,每批 1630、1630、740 个。数字对得上:一批的容量由参数长度决定,不由文件个数决定。

这这一点在写脚本时是有影响的,任何依赖「一次看得全」的写法都会在文件多起来之后失效。任何在命令里假设「一次调用能看到全部文件」的写法都会在文件多起来之后失效,包括用第一个参数当基准路径、用参数个数推导进度、或者把每个参数当独立任务编号。

参数位置和报错

加号要求 {} 出现在末尾,位置写错 find 直接报错:

== 3. {} 的位置写错时 find 怎么说 ==
  写成 'echo a {} b +':退出码 1
  stderr:find: -exec: no terminating ";" or "+"

分号没有这个约束,{} 可以出现在任意位置,代价是每个文件一次调用。

退出码也不一样

这一条是实测出来的,跟直觉相反:

== 5. 命令失败时的退出码 ==
  -exec false {} 分号:find 退出码 0
  -exec false {} 加号:find 退出码 1

同一条 -exec false {},分号写法下 find 的退出码是 0,加号写法下是 1。放在 set -e 的脚本里,这两种写法会不会中断后续步骤是不一样的。分号写法下要判断结果,得在命令里自己记状态再返回,不能指望 find 的退出码。

这里说的是本机这版 find(BSD)。GNU find 上这两种写法的退出码没做对照,不写结论。

什么时候该用哪个

分号适合命令本身需要独占运行的场合,比如调用会去写同一个日志文件,或者要逐条读输出做判断。它的可预期性也更好,一条文件一条结果,跑完就能对上账。

加号适合纯计算或者纯读写的批处理命令,统计和校验这一类。代价是三条:一批里能看到多少文件由参数长度决定;命令拿到的是参数列表而不是文件列表,分隔符要自己处理;出错时得靠日志判断是哪一批。真想控制批大小,就得自己切列表交给 xargs 那种带 -n 的工具,不能指望 find 攒到满。

拆到目录级别还有一个更省的做法:-execdir 会在文件所在目录里执行,配合 + 能把参数变成相对文件名,路径短、批更大。这一条跑的是同一层目录,两种写法的参数长得一样,跨层目录才看得出差别,所以没写进上面的数字里。

我的取舍是:批处理脚本用加号,但在外面套一层,把每批的处理条数打到日志里。原因就是前面那 1630、1630、740,三批的边界不固定,重跑一次可能变成别的切法,出了错得知道是哪一批。

    🤞 分享