find 命令是很常用的shell命令,本文通过下面3个常见问题来剖析find的用法
1 按照时间查找文件
2 对路径中包含空格的文件进行查找
3 除去某些目录
1 按照时间查找文件
通过使用-ctime -mtime -atime可以指定特定时间这三个参数的使用方法是相似的,但是意义不同,使用man find来看其作用
看上去atime比较明确,但是ctime和mtime差不多,下面是这三个参数针对的具体时间。
ctime 在写入文件、更改所有者、权限或链接设置时随 Inode 的内容更改而更改的
mtime 在文件写入时会改变
atime 在读取文件或者执行文件时更改
根据man的介绍,n的写法有三种
说实话,man里这个介绍只能让用户处于似懂非懂,想写又怕写错的混沌状态中,所以有必要总结一个图表,来形象的表示这堆加加减减。
用下面的图表就可以完全搞清楚时间的表示法:
1 对于n来说,表示的是一个确定的时间段,时间跨度是1天(24小时),n表示的是远端距离当前的距离,单位是天。
2 对于+-n来说,表示的是以某个点为基准点,向某个方向偏移的区间,n表示基准点距离当前的距离,单位是天。
2 对路径中包含有空格的文件进行查找
如果单独使用find命令查找带有空格的文件或目录是没有问题的,下图中的blank name就含有空格,find可以正常使用。
若将含有空格的文件名通过管道输送给后端命令,则会出问题
后面的xargs显然把blank name切分为两个目录,分别送给了grep导致grep无法找到两个名为blank和name的目录,这跟xargs缺省的工作方式相关,xargs会将前面管道传送来的数据按照空格/换行符切分,并送给后端命令作为参数。使用man xargs可以看到其文档对此工作方式的介绍:
有下面两个方法解决这个问题
方法1 为find 添加print0,为xargs添加 -0
find . -print0 | xargs -0 grep -n 'voidccc'
这是我最喜欢的方法,在find的文档里已经有介绍,使用man find查看一下:
使用-print0指示find用null字符来代替换行符将结果输出,同时用-0告知xargs使用null来分割输入,作为参数送给grep。
方法2 使用find -exec
find -exec grep -n 'voidccc' {} \;
这样空格不会被切分,但是注意一个细节,如果只执行上面的命令,文件名不会被显示(环境为CentOS6.4 GNU grep 2.6.3),如果要显示文件名,别忘记为grep加上 -H参数
像下面这样:
3 除去某些目录
最强烈的需求大概来自于使用svn的同学需要将.svn目录从查找内容中除去,网上有很多资料介绍这种用法,但是往往含糊不清,这里必须深入分析一下。
不得不涉及到find的语法,通过man find查看
find命令格式看似复杂,有一串的可选项,其实常用的功能不需要那么多,我们可以简化为下面的样子
find [path] [expression]
这里的path很好理解,可以不写,默认值为当前目录
而expression可以是options或者tests或者actions三者之一,expression与expression之间通过operators连接,这在文档里也有叙述
在最常被用到的参数里,属于options的是下面几个,options总是返回true
-mindepth
属于tests的是下面几个,tests返回true或者false
-ctime
-wholename
-size
属于actions的是下面几个,actions有副作用,返回true或者false,对于prune来说,永远返回true。
-prune
而operators可以是下面几个,其中简单版本(! -o -a)都是POSIX compliant,看来POSIX标准的制定者非常为开发人员着想,把标准都设计成最简单形式了。operators的一个重要特性是支持短路运算,我们也正是利用了这个特性。
取反:
! expr1 //POSIX compliant
-not expr1 //等效于第一个
与运算:
erpr1 -a expr2 //POSIX compliant
erpr1 -and expr2 //等效于第一个
expr1 expr2 //不写等效于第一个
或运算:
expr1 -o expr2
expr1 -or expr2
研究清楚了find的基本用法,可以给出答案,使用下面3种写法都可以达到目的
find -wholename "*.svn" -prune -o -print
find -wholename "*.svn*" -o -print
find ! -wholename "*.svn*"
依次拆解这三条命令,添加了红色的[]代表一个expression,蓝色代表被我补齐的内容,expr1,2,3是为了后续叙述方便
find [-wholename "*.svn*"]-a[-prune] -o[-print]// expr1 -a expr2 -o expr3
find [-wholename "*.svn*"]-o[-print]// expr1 -o expr2
find ! [-wholename "*.svn*"]-a[-print]// !expr1 -a expr2
命令1,如果路径包含.svn,则expr1 为true,导致expr2执行判断,但是expr2永远为true,所以expr1 -a expr2已经为true,所以短路规则-print不会被执行,此路径无法被打出来,过滤掉了。如果包含.svn,则expr1为false,这导致expr1 -a expr2为false,所以必须执行后面的-print,所以该路径被打印出来。
命令2,如果路径包含.svn,则expr1为true,直接短路掉-print,路径无法打印,如果路径不包含.svn,则expr1为false,导致expr2被执行,则路径打印。
命令3,没有写任何actions,则系统补全一个-print,而这个-print和前面是-a关系,所以如果路径包含.svn,则expr1为true,!expr1为false,导致-print被短路,路径不被打印。如果路径未包含.svn,则expr1为false,!expr1为true,则-print需要被执行导致路径被打印。
综合分析,上面三条语句都可以达到忽略路径中的.svn文件/目录的功能,一个稍显奇怪的地方是-prune反而变的可有可无。