find用于搜索文件或目录,功能非常强大。该工具是findutils包提供的,该包中还包括一个老版本的oldfind工具,以及另一个非常强大的xargs命令,在搜索文件时,如果还要对搜索的文件进行后续的处理,一般都会结合xargs来实现。但本文并不过多涉及xargs,如果要了解xargs用法,见我的另一篇关于xargs的总结xargs的原理剖析及用法详解,目前在网上暂时还没找到比这篇文档更详细的xargs说明。
find搜索是从磁盘进行指定目录开始扫描,而不是从数据库搜索。
find [path…] [expression_list]
find [path…] [expression_list]
expression分为三种:options、test、action。对于多个表达式,find是从左向右处理的,所以表达式的前后顺序不同会造成不同的搜索性能差距。
find首先对整个命令行进行语法解析,并应用给定的options,然后定位到搜索路径path下开始对路径下的文件或子目录进行表达式评估或测试,评估或测试的过程是按照表达式的顺序从左向右进行的(此处不考虑操作符的影响),如果最终表达式的表达式评估为true,则输出(默认)该文件的全路径名。
对于find来说,一个非常重要的概念:find的搜索机制是根据表达式返回的true/false决定的,每搜索一次都判断一次是否能确定最终评估结果为true,只有评估的最终结果为true才算是找到,并切入到下一个搜索点。
1.2.1 expression operators
操作符控制表达式运算方式。确切的说,是控制expression中的options/tests/actions的运算方式,无论是options、tests还是actions,它们都可以给定多个,例如find /tmp -type f -name “*.log” -exec ls ‘{}’ \; -print,该find中给定了两个test,两个action,它们之间从前向后按顺序进行评估,所以如果想要改变运算逻辑,需要使用操作符来控制。
注意,理解and和or的评估方式非常重要,很可能写在and或or后面的表达式不起作用,而导致跟想象中的结果不一样。
下面的操作符优先级从高到低。
关于and和or操作符,一定要明确的是and后的表达式操作的对象是前面表达式的结果,而or操作符的对象则是前面评估后为假时文件。
例如:
find /tmp -type f -name “*.log“
它是一个and操作符,-name表达式是在-type筛选的结果基础上再匹配文件名的。但如果是:
find /tmp -type f -o -name “*.log“
则-type f返回真的文件直接执行它后面默认的-print,而不满足-type f的才会去被-name评估,所以返回结果中即有任意普通文件,也有任意log文件,但两者同名的文件只返回一次。
总之,and和or对于find的影响比较大,后面会专门用一节彻底搞懂operator和action来详细解释。另外,在后文find深入用法示例中还有一个关于”忽略目录“的例子,它很好的解释了操作符的行为。
1.2.2 expression-options
options总是返回true。除了”-daystart”,options会影响所有指定的test表达式部分,哪怕是test部分写在options的前面。这是因为options是在命令行被解析完后立即处理的,而test是在检测到文件后才处理的。对于”-daystart”这个选项,它们仅仅影响写在它们后面的test部分,因此,建议将任何options部分写在expression的最前面,若不如此,会给出一个警告信息。
1.2.3 expression-tests
find解析完命令行语法之后,开始搜索文件,在搜索过程中,每次检测到的文件都会被test expression进行测试,符合条件的将被保留。
数值部分可以设置为以下3种模式:n可以是小数。
+n:大于n
-n:小于n
n :精确的等于n
对于文件大小而言,文件的大小是精确的,指定100KB,则比对的值必定是100KB。此时(+ -)n和字面意思是一样的。
但对于时间而言,时间是有时间段的,例如指定前第四天,第四天也整整占用了一天,所以(+ -)n和文件大小的计算方法是不一样的。
find在计算以天数为单位的时间时,默认会转换为24小时制,除非同时指定了“-daystart”这个选项,这在前面已经解释过了。例如当前时间为5月3号17:00,那么计算“atime +1″的时候,真正计算的是24*1=24小时之前,又因为前48小时到前24小时之间都属于前一天内,所以其搜索的是是5月1号17:00以前被访问过,若指定了“-daystart”,则计算的是5月2号00:00之前被访问过。
具体的选项如下:
【文件大小或内容类测试条件】
【文件名或路径名匹配类测试条件】
【权限类测试条件】
【所有者所属组类测试条件】
【时间戳类测试条件】
【软硬链接类测试条件】
【杂项测试】
1.2.4 expression-actions
actions部分一般都是执行某些命令,或实现某些功能。这部分是find的command line部分。
一定要注意,action是可以写在tests表达式前面的,它并不一定是在test表达式之后执行。
例如:
它将输出/tmp下所有文件,而不是输出满足-type f -name “*.txt”的文件,因为-print总是返回true,它是第一个表达式,所以直接输出整个目录,后面的表达式-type f -name “*.txt”虽然在-print之后也被评估了,但是却没有对应的action,所以它们的匹配行为并没显示出来。如果在表达式-type f -name “*.txt”之后加上另外一个action,那么这个action将只针对匹配到的文件进行操作。
可以看到结果中,只有满足条件的两个文件才执行了ls命令。
还可以写多个action,写在哪里要注意它们的执行顺序。其实,不推荐使用除了”-print”、”-print0″和”-prune”外其他所有的action,如有需要,应该配合xargs命令来实现目的。
find的逻辑是很严格的,但是如果没有深究过,可能会出现不容易理解的偏差。
下面,我对and和or操作符,并以-print这个action为例来详细说明它们的关系。在测试的时候,find的”-D debug”选项非常好用,它能让我们知道find是按照什么逻辑处理各个选项的。debug有几种类型,这里选择”-D rates”这种调试类型。
首先要明确一个结论,and的优先级高于or。
例如, -name “*.log“ -o -name “*.txt“ ,搜索到1.txt文件时,*.log评估结果为假,于是评估*.txt,评估为真,于是整个-o逻辑返回真。
第二个结论:如果find评估完所有表达式后发现没有action(-prune这个action除外),则在最末尾加上-print作为默认的action。注意,这个默认的action是在评估完所有表达式后加上的。且还需注意,如果只有-prune这个action,它还是会补上-print。
以下是准备数据:
1.3.1 测试”and”操作符
现在先测试”expr1 -a expr2″中的”-a”。
用”-D rates”来看调试结果:
将最后一行进行简化,删掉所有中括号中的内容,得到的结果是:
所以,上面的语句执行逻辑是:
可见,find自动补上了-print这个默认的action,且逻辑是评估-name,在-name为真的基础上再评估-type,最后在-type为真的基础上评估-print,也就是将log后缀且是普通文件的文件打印出来。
这里的-name为什么被修改到了-type的前面去?这是因为find自带优化功能,它会评估各个表达式的开销,将开销小的表达式放在前面,以便优化搜索。但要注意的是,优化后的表达式和我们给出的find命令行的结果不会有任何出入,它们在逻辑上是相同的,只是更换了一些表达式的前后位置。
然后,按照前面的结论,猜测下面3个find语句的逻辑:
上面3个find都给出了action,所以find不会再补上默认的action:-print。
所以,上面3个语句分别等价于下面3个语句:
唯一需要注意的是,有时候某些expr后面没有action,这部分的expr将不会做任何处理,正如上面第二个语句,-name *.log后并不会补上-pinrt,所以对于find来说,-a -name *.log这个条件完全是多余的表达式。
以下是上述3个find命令的输出结果以及分析。
-type f评估后得到所有普通文件,-name再在普通文件的基础上筛选文件名后缀为”.log”的文件,最后评估-print输出”.log”文件。
-type f评估后得到所有普通文件,然后在普通文件的基础上评估-print,它直接将所有普通文件都输出,再在输出的文件的基础上,评估-name,得到log文件,但之后没有action,所以评估得到log文件的结果作废。
-type f评估后得到所有普通文件,然后在普通文件的基础上评估-print,它直接将所有普通文件都输出,再在输出的文件的基础上,评估-name,得到log文件,再对得到的log文件评估-print,它将再次输出log文件,所以结果中将输出两次log文件。
1.3.2 测试”or”操作符
有了上面的基础,再理解or操作符就简单多了。
直接猜下面4个命令的等价语句:
显然,上面第三个语句中的-o -name *.log是多余的。
以下是这4个命令的输出结果以及分析。
-type f评估后得到所有普通文件,但因为只有-o前面的表达式为假时才会评估-o后面的表达式,所以将会对非普通文件评估-name *.log,由于本示例中/tmp下没有非普通文件,所以-name将得不到结果,于是评估得到普通文件,最后在普通文件的基础上评估-print,即输出普通文件。
-type f评估后得到所有普通文件,然后对非普通文件评估(-name *.log -a -print),由于没有非普通文件,所以这个括号评估结果为空。由于-type f后面没有给action,所以评估得到普通文件的结果也作废。因此,这个find命令什么都不会输出。
如果在/tmp下创建一个以”.log”为后缀的目录,则该find命令会输出这个目录。
-type f评估后得到所有普通文件,然后直接输出,然后再对未输出的文件评估-name,也就是找出”.log”结尾的非普通文件,但即使如此,后面也没有action,所以这里的-name是多余的。所以该命令等价于 find /tmp -type f -print 。
注意,-name评估的对象是未被-print输出的文件,而不是未被-type评估的文件。虽然在本示例中是等价的,但如果-print前还有-a条件,如 find /tmp -type f -a “*.txt“ -print -o -name “*.log“ ,这里的-name评估的对象是未被输出的文件,也就是(-type f -a -name .txt)的取反,而不是”-type f”的取反,所以log结尾的普通文件也会被-name评估,但因为没有后续的action,评估的结果会作废。
-type f评估后得到所有普通文件,然后直接输出,然后对非普通文件评估(-name *.log -a -print),由于本处示例中没有以”.log”结尾的非普通文件,所以这个括号的评估结果为空。但如果在/tmp下创建一个以”.log”为后缀的目录,则该find命令会也输出这个目录。
(1). 指定目录的搜索深度
例如搜素/etc目录下的”.conf”文件,但不搜索任何子目录。
find /etc -maxdepth 1 -type f -name “*.conf“
注意,”-depth”、”-maxdepth”和”-mindepth”是find的option表达式,所以它们写在任意位置都会对test和action产生影响,所以它的位置可随意书写。
(2). 指定目录的处理顺序:-depth
“-depth”选项用于改变find使得它先搜索目录中的文件,然后才处理目录本身。默认情况下,find查找文件和目录的顺序是:假如/tmp下有a文件、b和c两个目录,b和c目录中都有一些文件和目录。
先查找/tmp本身
再查找/tmp下的各个文件,如a
再按序查找/tmp下的第一个目录,如b
再查找b中的文件。
查找/tmp下的下一个目录,如c
查找c中的文件
如果b和c目录中还有其他目录,则依照上面的处理规则进行处理。
即/tmp –> /tmp中的文件 –> /tmp中第一个目录 –> 第一个目录中的文件 –> 第一个文件中的目录 –> … –> /tmp中第二个目录 –> 第二个目录中的文件 –> … –> /tmp中的第三个目录 –> …/tmp中的最后一个目录。
如下面的图所示。
如果指定了-depth,则先处理目录中的文件,再处理目录本身。在Linux一切皆文件,子目录也是文件。
例如,下面在/tmp目录下新建一个目录/tmp/tmp,并在子/tmp下创建文件a,目录b和c,其中分别有一些文件。使用-depth参数运行find。
上面的find工作过程是:先找到/tmp/tmp,准备处理/tmp/tmp下的文件a、b、c(目录也是文件),在这里的处理顺序是b、c、a(为什么这样的顺序我也不知道),处理b的时候发现它是一个目录,转去处理b目录里的文件,处理完b中的文件后处理b目录,再处理c,发现是目录,转去处理c中的文件,处理完c中的文件后处理a文件。
可以看到.x隐藏文件是在中途被处理的。
最后看看不加”-depth”的搜索顺序。
(3). 忽略搜索的结果:-prune
一般只考虑忽略目录,不考虑忽略文件,一般忽略文件时会给出警告。再者,忽略某种文件可以使用其他条件来忽略。忽略目录后,Linux将直接不处理忽略的位置。
-prune需要放在定义忽略表达式的后面,之所以会如此,请看示例并考虑true和false进行理解。因为要忽略的是目录,所以一般都只和”-path”配合,而不跟”-name”配合,实际上和-name配合的时候会给出警告信息。
例如,查找/tmp中的”.log”文件,但排除/tmp子abc目录内的”.log”文件。
为何这里使用”-o”操作符而不是”-a”操作符呢?先将上述find表达式进行分解。第一个表达式-path “/tmp/abc”可以搜索出/tmp/abc文件,它可能是目录,也可能是文件,但无论它是什么文件,该表达式返回的总是true;第二个表达式是action类的”-prune”,它和第一个表达式是”expr1 expr2″这样的操作符格式,它等价于and逻辑关系,所以-path “/tmp/abc” -prune表示不进入匹配到的/tmp/abc目录(若abc为文件,则给出警告信息),由于下一个表达式是使用”-o”连接的,所以到此为止就确定了该目录/tmp/abc,且是不进入的,由于返回true,-prune会将结果输出出来。
继续,目的是搜索出非abc目录下的log文件,它的表达式应该是-name “*.log”。但由于前面返回的是true,如果使用”-a”选项连接前后,则表示后面的表达式的操作对象是/tmp/abc,但前面的逻辑是不进入/tmp/abc,所以将得不到任何结果。
而使用”-o”连接,则表示-name “*.log”的操作对象是path部分(即/tmp目录)除了/tmp/abc的其余文件,它将找出/tmp下所有的log文件,但由于前面明确表示了不进入/tmp/abc目录,所以就实现了忽略/tmp/abc目录的作用。
它的等价命令如下:
应该注意到了,上面的/tmp/abc目录也出现在结果中了,但是它并非log文件。这是因为-prune的副作用,find认为-prune不是一个完整的action,它会补上-print。如果想要完完全全的忽略该目录,则可以使用下面的方式,在-prune之后加上-false强制使得该段表达式返回false,这样该段结果就不会被输出。
-prune的一个弱点是不适合通过通配符来忽略目录,因为通配符出来的很可能导致非预期结果。
所以想要忽略多个目录,最好的方法是多次使用-path,但要注意它们的逻辑顺序。例如,搜索/tmp下所有log文件,但忽略/tmp/abc和/tmp/xyz两个目录中的log文件。
所以完整的写法是:
(4). 不显示待搜索目录本身
在find显示结果的时候,如果没有表达式过滤掉目录本身,那么目录本身也会被显示出来,但是很多时候这时不必要的。一般用于只显示一级目录下所有的文件,但不包括目录本身。
要过滤掉目录本身,方式也很简单,多使用一个匹配表达式即可。
(5). 搜索指定目录下非空文件
“-empty”测试条件用于测试文件是否非空,对于目录而言则是目录为空目录。一般可用于在搜索时,排除空文件或空目录,所以一般和”!”或”-not”一起使用。
例如,搜索/tmp下非空文件和非空目录。
[root@xuexi tmp]# find /tmp/tmp ! -empty
原文链接:https://www.cnblogs.com/f-ck-need-u/p/6995529.html
原创文章,作者:优速盾-小U,如若转载,请注明出处:https://www.cdnb.net/bbs/archives/32327