北肙

当你不能够再拥有,唯一可以做的,就是令自己不要忘记。

揭密’find’无匹配,搭配’xargs’却能列出文件的原因

问题描述

单独用find查找满足条件的文件或目录时并无匹配,如下:

  1. 列出/tmp目录下文件以作展示[1]
cd /tmp && ls -l
# cd /tmp/ && ls -l
total 64
srwxrwx--- 1 netdata netdata    0 Sep  1 19:08 netdata-ipc
drwxrwxr-x 2 portage portage   40 Sep  1 19:08 portage
drwxrwxr-x 2 root    utmp      40 Sep  1 20:08 screen
-rw-r--r-- 1 root    root    1351 Sep  2 00:00 tmp0cznslq0
-rw-r--r-- 1 root    root    1315 Sep  3 00:00 tmp170scwam
-rw-r--r-- 1 root    root    1259 Sep  3 00:00 tmp3a69_fzu
-rw-r--r-- 1 root    root    1268 Sep  3 00:00 tmp6d_oxnxz
-rw-r--r-- 1 root    root    1240 Sep  3 00:00 tmp7iyopakk
-rw-r--r-- 1 root    root    1297 Sep  3 00:00 tmp7y_n8v26
-rw-r--r-- 1 root    root    1328 Sep  2 00:00 tmp9rnmmbff
-rw-r--r-- 1 root    root    1329 Sep  2 00:00 tmp9y8grm2d
-rw-r--r-- 1 root    root    1289 Sep  3 00:00 tmpbzop6hd7
-rw-r--r-- 1 root    root    1250 Sep  2 00:00 tmpe4isdmly
-rw-r--r-- 1 root    root    1284 Sep  2 00:00 tmppuf5jmy_
-rw-r--r-- 1 root    root    1237 Sep  2 00:00 tmpqf8tkzv2
-rw-r--r-- 1 root    root    1291 Sep  3 00:00 tmpq_le14ux
-rw-r--r-- 1 root    root    1259 Sep  3 00:00 tmpu2azbu5n
-rw-r--r-- 1 root    root    1270 Sep  2 00:00 tmpxjfh_00o
-rw-r--r-- 1 root    root    1259 Sep  2 00:00 tmpxp6854h3
  1. 查找名称包含abc的目录
find /tmp -type d -name "*abc*" -print
结果为空。

但是,如果把上面find的结果管道给xargs,再用ls列出

find /tmp -type d -name "*abc*" -print | xargs ls -l
# find /tmp -type d -name "*abc*" -print | xargs ls -l
total 64
srwxrwx--- 1 netdata netdata    0 Sep  1 19:08 netdata-ipc
drwxrwxr-x 2 portage portage   40 Sep  1 19:08 portage
drwxrwxr-x 2 root    utmp      40 Sep  1 19:08 screen
-rw-r--r-- 1 root    root    1351 Sep  2 00:00 tmp0cznslq0
-rw-r--r-- 1 root    root    1315 Sep  3 00:00 tmp170scwam
-rw-r--r-- 1 root    root    1259 Sep  3 00:00 tmp3a69_fzu
-rw-r--r-- 1 root    root    1268 Sep  3 00:00 tmp6d_oxnxz
-rw-r--r-- 1 root    root    1240 Sep  3 00:00 tmp7iyopakk
-rw-r--r-- 1 root    root    1297 Sep  3 00:00 tmp7y_n8v26
-rw-r--r-- 1 root    root    1328 Sep  2 00:00 tmp9rnmmbff
-rw-r--r-- 1 root    root    1329 Sep  2 00:00 tmp9y8grm2d
-rw-r--r-- 1 root    root    1289 Sep  3 00:00 tmpbzop6hd7
-rw-r--r-- 1 root    root    1250 Sep  2 00:00 tmpe4isdmly
-rw-r--r-- 1 root    root    1284 Sep  2 00:00 tmppuf5jmy_
-rw-r--r-- 1 root    root    1237 Sep  2 00:00 tmpqf8tkzv2
-rw-r--r-- 1 root    root    1291 Sep  3 00:00 tmpq_le14ux
-rw-r--r-- 1 root    root    1259 Sep  3 00:00 tmpu2azbu5n
-rw-r--r-- 1 root    root    1270 Sep  2 00:00 tmpxjfh_00o
-rw-r--r-- 1 root    root    1259 Sep  2 00:00 tmpxp6854h3

注意:没有匹配的find管道给xargs之后居然列出了/tmp下的全部文件。

提问

  1. find查找文件再用xargs配合rm -rf删除,如果匹配失败,则删除起始目录下所有文件,会不会很危险???
  2. find搭配-exec rm -rf则相对安全一些???

验证猜想

  1. find配合-exec删除名称包含abc的目录
find /tmp -type d -name "*abc*" -exec rm -rf {} \;
结果不出所料,没有匹配到,啥也没删掉。
  1. /tmp目录下用find配合xargsrm -rf删除名称包含abc的目录
find /tmp -type d -name "*abc*" | xargs rm -rf
# find /tmp -type d -name "*abc*" | xargs rm -rf
# 
# ls -l
total 64
srwxrwx--- 1 netdata netdata    0 Sep  1 19:08 **netdata-ipc**
drwxrwxr-x 2 portage portage   40 Sep  1 19:08 **portage**
drwxrwxr-x 2 root    utmp      40 Sep  1 19:08 **screen**
-rw-r--r-- 1 root    root    1351 Sep  2 00:00 tmp0cznslq0
-rw-r--r-- 1 root    root    1315 Sep  3 00:00 tmp170scwam
-rw-r--r-- 1 root    root    1259 Sep  3 00:00 tmp3a69_fzu
-rw-r--r-- 1 root    root    1268 Sep  3 00:00 tmp6d_oxnxz
-rw-r--r-- 1 root    root    1240 Sep  3 00:00 tmp7iyopakk
-rw-r--r-- 1 root    root    1297 Sep  3 00:00 tmp7y_n8v26
-rw-r--r-- 1 root    root    1328 Sep  2 00:00 tmp9rnmmbff
-rw-r--r-- 1 root    root    1329 Sep  2 00:00 tmp9y8grm2d
-rw-r--r-- 1 root    root    1289 Sep  3 00:00 tmpbzop6hd7
-rw-r--r-- 1 root    root    1250 Sep  2 00:00 tmpe4isdmly
-rw-r--r-- 1 root    root    1284 Sep  2 00:00 tmppuf5jmy_
-rw-r--r-- 1 root    root    1237 Sep  2 00:00 tmpqf8tkzv2
-rw-r--r-- 1 root    root    1291 Sep  3 00:00 tmpq_le14ux
-rw-r--r-- 1 root    root    1259 Sep  3 00:00 tmpu2azbu5n
-rw-r--r-- 1 root    root    1270 Sep  2 00:00 tmpxjfh_00o
-rw-r--r-- 1 root    root    1259 Sep  2 00:00 tmpxp6854h3

意外不意外,惊喜不惊喜,啥也没动???欲知到底为何,且听下文分解。

揭密

通过执行两个简单命令,即可一目了然。

cd /tmp && ls -l

输出不再贴,雷打不动就那点内容。

cd /tmp && rm -rf

那么本文所探究的谜题答案将是:

并不是find遇到自己stdout为空(即匹配失败)时错将其stdin(标准输入,此时即/tmp)通过管道传递给了xargs(或者反过来说,xargs错把findstdin当成的自己的stdin),而是xargs命令后尾随的lsrm对空参数的处理。 当ls后不跟随文件参数时,即列出当然路径(pwd)下的文件;而rm则直接返回0且无输出。

如果不放心,用find管道xargs ls -l的命令在其它没有文件的目录下执行:

# cd /root/Documents/
# ls -l
total 0
# find /tmp -type d -name "*abc*" -print | xargs ls -l
total 0
# 
#

掌握核心科技后,面对重重谜团,也不会感到一丝意外,列出的就是/root/Documents/目录,绝对没错。

题外话


Q1. 本文始,提前把目录切到了/tmp,实际操作中如果find的目录和当前所在目录并不一致,会不会有删除文件的风险?

理论上并不存在这种可能性。

实际上为保险其见还需要再验证一下:

# pwd
/root/Documents
# ls /tmp
netdata-ipc  tmp170scwam  tmp7y_n8v26  tmpe4isdmly  tmpu2azbu5n
portage      tmp3a69_fzu  tmp9rnmmbff  tmppuf5jmy_  tmpxjfh_00o
screen       tmp6d_oxnxz  tmp9y8grm2d  tmpqf8tkzv2  tmpxp6854h3
tmp0cznslq0  tmp7iyopakk  tmpbzop6hd7  tmpq_le14ux
# 
# find /tmp -type d -name "*abc*" | xargs rm -rf 
# 
# 
# ls /tmp
netdata-ipc  tmp170scwam  tmp7y_n8v26  tmpe4isdmly  tmpu2azbu5n
portage      tmp3a69_fzu  tmp9rnmmbff  tmppuf5jmy_  tmpxjfh_00o
screen       tmp6d_oxnxz  tmp9y8grm2d  tmpqf8tkzv2  tmpxp6854h3
tmp0cznslq0  tmp7iyopakk  tmpbzop6hd7  tmpq_le14ux
#

果然不出所料。 但是:如果手误在rm -rf后面加了*号,后果是灾难性的。


Q2: find匹配的结果传递给xargs的时候是找出一个送出一个,还是找完了一并送出?[2]

 一次全送出。

前提:find查找/tmp下以tmp开头的常规文件会匹配到多个;如果一次送出则输出堆叠在一起,如果多次送出,则输出会是单文件多列的形式。

# ls tmp6d_oxnxz tmp7iyopakk 
tmp6d_oxnxz  tmp7iyopakk
# 
# 
# ls tmp6d_oxnxz; ls tmp7iyopakk 
tmp6d_oxnxz
tmp7iyopakk

验证:

find /tmp -type f -name "tmp*" | xargs ls  # 注意是ls,没有跟参数-l
# find /tmp -type f -name "tmp*" | xargs ls
/tmp/tmp0cznslq0  /tmp/tmp7iyopakk  /tmp/tmpbzop6hd7  /tmp/tmpq_le14ux
/tmp/tmp170scwam  /tmp/tmp7y_n8v26  /tmp/tmpe4isdmly  /tmp/tmpu2azbu5n
/tmp/tmp3a69_fzu  /tmp/tmp9rnmmbff  /tmp/tmppuf5jmy_  /tmp/tmpxjfh_00o
/tmp/tmp6d_oxnxz  /tmp/tmp9y8grm2d  /tmp/tmpqf8tkzv2  /tmp/tmpxp6854h3
#

上文输出中,符合条件的文件堆叠在一起列出,很明显,find管道给xargs的时候是一堆文件一起扔。


Q3: find加参数-exec对匹配到的结果进行操作时,来一个砍一个,还是来齐了一波砍?

来一个砍一个。

验证:

# find /tmp -type f -name "tmp*" -exec ls {} \;
/tmp/tmp7y_n8v26
/tmp/tmp3a69_fzu
/tmp/tmp6d_oxnxz
/tmp/tmpu2azbu5n
/tmp/tmpq_le14ux
/tmp/tmpbzop6hd7
/tmp/tmp7iyopakk
/tmp/tmp170scwam
/tmp/tmpe4isdmly
/tmp/tmppuf5jmy_
/tmp/tmpxjfh_00o
/tmp/tmp9rnmmbff
/tmp/tmp0cznslq0
/tmp/tmpxp6854h3
/tmp/tmpqf8tkzv2
/tmp/tmp9y8grm2d

ls命令并未加-l参数,但列出的单个文件却独占一行,表明符合FIFO(First In First Out,不知道卖弄得对不对。)的处理方式。

那么问题来了,如果是清理文件,find配合-exec一次删一个,而xargs是一次删一批;则: find单独用的时间是:查找n个文件的时间 + 依次删除n个文件的时间; find管道xargs的时间是:查找n个文件的时间 + 一次删除n个文件的时间;

那么,该问题可以转化为:用rm处理文件时,用for循环挨个删一遍快还是一次全部删掉快?按理说,应该是一次来得痛快。用了for之后调用额外的函数肯定有额外的性能开销,当然,此处全靠猜,并不打算验证。 因为,随后就有但是。

但是,即然选择用find删除文件,通常情况下,面对的相对大量甚至海量的输出,rm性能如何还在其次,如此大量的文件路径缀在rm命令之后直接就报错了。大概是args too long之类的吧。


Q4: 虽然find-exec参数会把匹配按个传递,如果我就是想一次打包带走呢?

也有办法。那就是-exec command {} +参数

注意:和-exec command ;参数的格式一样,;+都需要\来逃逸。

示例:

# find /tmp -type f -name "tmp7*" -exec ls {} \+
/tmp/tmp7iyopakk  /tmp/tmp7y_n8v26
#

依然是熟悉的目录和熟悉的文件,改成tmp7*是想让输出少一些,毕竟老拿命令输出来凑字数,时间长了也是良心难安呐。且看,把;替换成+之后,便能一波收了。

留个尾巴

世人皆言,删除海量文件时用-delete-exec rm -rf| xargs rm -rf效率高,难道是find也有个缓存或消息对列啥的,主进程前脚往里送,-delete后脚往外拽?有点像那啥物理还是数学课上一边往池子里注水一边又放掉的题目,以前觉得简直是愚蠢至极,难道这才是它高明之处?

One More Thing

9月12号即将迎来某果新一年的秋季发布会,不知道这次的one more thing会不会有点惊喜。不等了,咱先自己安排上。

关于-delete-depth-prune的配合使用

  1. -depth:
  • 在处理目录自身之前先处理该目录之中的文件;
  • 使用-delete时默认激活-depth(难道是因为-delete本身不能删除非空目录);
  • 手册建议执行如,先列出匹配再-delete删除的操作时,应明确指定-depth参数,以免结果难以预料(掌握核心科技也不行);
  1. -prune:
  • 保护指定的目录不被误操作;
  • 如果和-depth一同出现,则-prune失效
  • 因为-delete默许了-depth选项,因此-delete-prune一起也会让后者失效;
  • 示例:find . -path ./src/emacs -prune -o -print (抄了个man手册,没咋用过)
  1. -delete
  • 不能删除非空目录(不晓得指定-depth后会不会自动先清文件再删目录??)
  • 配合-ignore_readdir_race参数可以忽略当对匹配到的文件进行操作时该文件已消失时的报错,此举不输出错误,不会把命令返回值置为非00即命令执行成功),如果没有其它错误,则-delete操作依然返回真。

  1. Linux下一切皆文件,故此篇中文件即表示文件和目录,不再缀述。 ↩︎

  2. 关于xargs对管道过来的stdin再加工,成列还是成行,参考Linux Man手册。 ↩︎

Leave a Reply

Your email address will not be published. Required fields are marked *