聊一聊 find | xargs 与 find -exec

1,145 阅读4分钟

本文需要读者大致了解find 和 xargs 的用法,不了解的读者可以先去了解一下然后再回来随着文章一起学习,这样学习效果会更好。

find 命令用来搜索符合给定条件的文件集合,搜索出来的结果可以通过两种方式以参数的形式传递给后续的命令执行。

这两种方法是

  • 通过 find 自带的 “-exec” 参数。
  • 通过  | (管道) 传递给 xargs。

1. 目标

我们将使用 find 命令和 xargs 命令组合搜索当前文件夹,查找所有python文件里的带有"class"字符串的代码行行数

2. 实现

我们可以给出四种不同的实现,稍后会解释这四种方式之间有什么不同

# -exec \;
find . -name "*.py" -type f -exec grep 'class' {} \; | wc -l
14029

# -exec \+
find . -name "*.py" -type f -exec grep 'class' {} \+ | wc -l
14029

# find | xargs -n1
find . -name "*.py" -type f | xargs -n1 grep 'class' | wc -l
14029

# find | xargs
find . -name "*.py" -type f | xargs grep  'class' | wc -l
14029

这四个命令执行结果是一样的, 但是在实际执行时发现不同的命令等待返回的时间却相差很大,那么如何获取命令执行的准确时间呢,这里可以使用linux自带的命令 time

# -exec \;
time (find . -name "*.py" -type f -exec grep 'class' {} \; | wc -l)
0.12s user 0.57s system 9% cpu 7.140 total

# -exec \+
time (find . -name "*.py" -type f -exec grep 'class' {} \+ | wc -l)
0.06s user 0.33s system 35% cpu 1.106 total

# find | xargs -n1
time (find . -name "*.py" -type f | xargs -n1 grep 'class' | wc -l)
0.15s user 0.92s system 14% cpu 7.472 total

# find | xargs
time (find . -name "*.py" -type f | xargs grep  'class' | wc -l)
0.06s user 0.42s system 45% cpu 1.066 total

time用于分析命令的各项执行时间,包含了用户态时间和内核态时间,我们以第一条结果为例

0.12s user 0.57s system 9% cpu 7.140 total

这条结果意思是 用户态0.12秒,内核态0.57秒,命令执行期间cpu使用率9%, 总执行时间为7.140秒,这里有同学会问为什么用户态时间 + 内核态时间 != 总时间呢?其实命令进程在执行时并不是一直处于running状态,当进程被阻塞时是不会被统计到 用户态时间(user) 或者 内核态时间(system),但是依旧会被统计到总时间里(total),这也是为什么cpu使用率只有9%的原因。

3. 解释

3.1   find \;

find . [args] -exec [cmd] {} \;

{} 表示 被搜索到的结果集。

\; 表示 对于搜索到的结果集里的元素, 单独一个一个的以参数传递给 exec 后面的命令 ,也就是说 find了多少文件, 后面的 cmd 就会被执行多少次。

对于我们给出的实现里,这个命令相当于执行了 14029 次 

grep 'class' filename

3.2   find  \+

find . [args] -exec [cmd] {} \+

同上 {} 表示 被搜索到的结果集,

\+ 表示 对于搜索到的结果集里的元素, 一次全部传递给 exec 后面的命令, 也就是说无论 find 找到了多个文件, 都会一次性传递给后面的 cmd, 相当于 cmd 只会被执行一次

对于我们给出的实现里,这个命令相当于执行了 1 次 

grep 'class' filename1  filename2 filename3 ...

3.3   xargs -n1

xargs 作用是将标准输入转为命令行参数 传递给 cmd

这样说比较抽象不太清楚,我们实际例子来说明

cat ~/.bash_history | echo

这里我们打印出 .bash_history 内容将其通过 | 传递给 echo, echo是不会有任何输出的

但是如果我们使用了xargs

cat ~/.bash_history | xargs echo

这时 ehco 就会打印出 .bash_history 的内容了

find .[args] | xargs -n1 [cmd]

-n1  表示 对于管道里的参数 xargs 会一个一个的传递给 cmd 依次执行, 所以它等同于:

find . -exec [cmd] {} \;

3.4    xargs 

find .[args] | xargs [cmd]

如果 xargs 没有指定 -n 参数 , xargs会默认为 -n5000, 即 5000个参数一组 传递给后面的 cmd 执行, 在大部分情况下它等同于:

find . -exec [cmd] {} +;

在我们给出的实现里, 相当于

grep 'class' file1 file2 ... file5000grep 'class' file5001 file5002 ... 

 4 总结

所以我们在日常工作中如果需要用到   find | xargs 或者 find -exec 时,首选使用  find -exec {} \+ 或 find | xargs , 如果后边的 cmd 只支持传递一个参数时再使用 find -exec {} \; 或者 find | xargs -n1。

如果你对 find 和 xrags 的用法有补充或者对文中某个知识点有不同的看法, 欢迎在评论区里进行讨论😊。