问题描述
单独用find
查找满足条件的文件或目录时并无匹配,如下:
- 列出
/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
- 查找名称包含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
下的全部文件。
提问
- 用
find
查找文件再用xargs配合rm -rf
删除,如果匹配失败,则删除起始目录下所有文件,会不会很危险??? find
搭配-exec rm -rf
则相对安全一些???
验证猜想
- find配合-exec删除名称包含abc的目录
find /tmp -type d -name "*abc*" -exec rm -rf {} \;
结果不出所料,没有匹配到,啥也没删掉。
- 在
/tmp
目录下用find配合xargs
用rm -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
错把find
的stdin
当成的自己的stdin
),而是xargs
命令后尾随的ls
和rm
对空参数的处理。
当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
的配合使用
-depth
:
- 在处理目录自身之前先处理该目录之中的文件;
- 使用
-delete
时默认激活-depth
(难道是因为-delete
本身不能删除非空目录); - 手册建议执行如,先列出匹配再
-delete
删除的操作时,应明确指定-depth
参数,以免结果难以预料(掌握核心科技也不行);
-prune
:
- 保护指定的目录不被误操作;
- 如果和
-depth
一同出现,则-prune
会失效; - 因为
-delete
默许了-depth
选项,因此-delete
和-prune
一起也会让后者失效; - 示例:
find . -path ./src/emacs -prune -o -print
(抄了个man手册,没咋用过)
-delete
- 不能删除非空目录(不晓得指定
-depth
后会不会自动先清文件再删目录??) - 配合
-ignore_readdir_race
参数可以忽略当对匹配到的文件进行操作时该文件已消失时的报错,此举不输出错误,不会把命令返回值置为非0
(0
即命令执行成功),如果没有其它错误,则-delete
操作依然返回真。