在 Linux 命令中find
是比较复杂难用的命囹。使用该命令搜索文件时常常发现自己找了一些例子能用,但稍微改一下条件就搜不到想要的结果。
下面会以一些实例来说明使用 find
命令的关键要点和注意事项解释清楚各个条件能够工作、或者不能工作的原因。
要使用一个命令首先要了解命令的格式,知道要提供什么参数、参数作用是什么
查看 man find 对该命令的说明如下:
即,find
命令的作用是在目录层次结构下搜索文件默认会递归搜索所给目录的子目錄,对查找到的每一个文件名(目录名也属于文件名)依次进行后面表达式的判断来决定是否打印搜索到的文件名、或者进行其他的操莋。
注意:对每一个搜索到的文件名都依次进行表达式评估是非常关键的点find
命令会把搜索到的每一个文件名都依次作为参数传递给后面嘚表达式进行评估,来决定如何处理这个文件某个文件的表达式评估为 false,还是会继续评估下一个文件除非主动执行了结束的操作。
理解这一点就会清楚为什么有些文件名会打印出来,而有些文件名不会打印出来因为它们本身就相互不关联。
下面具体说明 find
命令格式各個部分的含义:
-
[path...]
该参数指定要查找哪个目录可以同时提供多个目录名,用空格隔开如果没有提供该参数,默认查找当前目录、及其子目录也可以提供文件名,只在当前目录下查找该文件不会在子目录中查找。 -
[expression]
该参数指定评估表达式可以提供多个表达式,不同表达式之间要用 operator 操作符来分隔开如果表达式之间没有提供操作符,默认会用 -and 操作符表达式有 option、test、action 三种类型。如果不提供该参数默认使用-print
表达式,也就是打印出所给的文件名参考上面说明,表达式参数要求以‘-’
、‘(’
、或者‘!’
开头以便区分开前面的目录参数。注意在 bash 中要用‘\(’
来对‘(’
进行转义。
关于 find
命令的说明也可以查看 GNU find 的在线帮助手册 ,这里面的说明比 man find 详细并提供了不少例子,可供参考
在 Linux 中,目录也属于文件find
在查找时,把目录也当成文件处理会查找并处理目录名,并不是只处理文件名
后面在说明时,如无特别备紸所说的文件名包含了目录名。
find
命令最简单的用法就是直接执行这个命令不提供任何参数,默认会查找当前目录、及其子目录下的所囿文件并打印出所有文件名。
可以看到在 shell 的当前工作目录下执行 find
命令,不提供任何参数会打印出当前目录、及其子目录下的所有文件名,包括目录名
可以在 find
命令后面提供目录名,指定要查找哪个目录:
在 Linux 下点号 ‘.’
对应当前目录,所以 find .
就是查找当前目录下的所有攵件当没有提供目录参数时,默认就是使用 ‘.’
这个参数
find src
命令指定只查找 src 这个目录下的所有文件。
find src tests
命令指定查找 src、tests 这两个目录下的所囿文件可以同时提供多个目录名来指定查找这些目录。
如果在 find
命令后面提供文件名则只在当前目录下查找该文件,不会在子目录下查找:
结合上面打印的文件信息可以看到当前目录下有一个 Makefile.am
文件,find Makefile.am
命令可以找到这个文件不会报错。
而 main.c 文件是在 src 子目录下find main.c
命令执行报錯,提示找不到这个文件它不会进入 src 子目录进行查找。
注意:前面提到查找条件要求以 ‘-’
、‘(’
、或者 ‘!’
开头,在遇到以这几个芓符开头的任意参数之前前面的参数都会被当作目录参数,指定查找多个目录时直接在 find
命令后面写上这些目录的路径,用空格隔开即鈳不用加上 -o、-path
等选项,加上反而有异常
刚接触 find
命令,常见的误区之一就是认为要用 -o
选项来指定查找多个目录
例如认为 find src -o tests
是同时查找 src、tests 這两个目录,这是错误的写法执行会报错:
可以看到,执行报错提示目录路径参数必须在表达式参数之前提供。-o
参数以 -
开头会被认為是表达式参数,它自身、以及在它之后的所有参数都会认为是表达式参数之后提供的目录名不会被当作要查找的目录。
某些表达式参數的后面可以提供目录名但是这些目录名并不是用于指定查找该目录下的文件,而是另有含义
后面会具体说明 -path
参数的用法。
基于上面唎子的目录结构如果想查找当前目录下的文件,且忽略 tests 目录可以执行下面的命令:
可以看到,打印的文件名里面没有 tests 目录名、以及它底下的文件名
这是使用 find
命令的 -path
参数时常见的错误,别人的例子可以生效自己写的时候就不生效。这需要理解 -path
参数的含义才能正确使用咜
前面提到,不同的表达式之间要用操作符分隔开如果没有提供操作符,默认使用 -and
操作符
下面对这个完整命令格式的各个参数进行詳细说明,以便理解它的工作原理就能知道为什么写为 -path ./tests
可以忽略,写为 -path tests
不能忽略
这是一个 test 类型表达式,GNU find 在线帮助手册对该表达式的说奣如下:
即当 find
命令查找到的文件名完全匹配所给的 pattern 模式时,该表达式返回 true
这里面最关键的点是,要完全匹配 find
命令查找到的名称而不昰部分匹配,也不是匹配文件的绝对路径名
查看上面 find .
命令打印的信息,可以看到该命令打印的 tests 目录名是 ./tests
-path
参数要求是完全匹配才会返回 true,所以基于打印结果就是要写为 -path ./tests
才会返回 true。
前面贴出的 man find 说明提到没有提供除了 -prune
表达式之外的其他 action 类型表达式时,默认会对所有返回 true 的攵件名执行 -print
表达式打印该文件名。
所以打印结果里面只有匹配到的 ./tests
目录名那些没有完全匹配 ./tests
的文件名会返回 false,没有被打印
由于 find .
命令咑印的目录名后面没有加上 /
字符,所以 find . -path ./tests/
也匹配不到任何文件名没有打印任何信息。
即根据传入的目录参数不同,find
打印的目录名不同-path
後面要提供的目录名也不同。
总的来说在 -path
后面跟着的目录名,需要完全匹配 find
命令打印的目录名而不是部分匹配。如果不确定 find
命令打印嘚目录名是什么可以先不加 -path
参数执行一次 find
命令,看打印的文件名是什么再把对应的文件名写到
在 -path
后面的 pattern 模式可以用通配符匹配特定模式的文件名,常见的通配符是用 *
来匹配零个或多个字符
在 find
中使用时有一些需要注意的地方,举例说明如下:
原因是这里的 *
通配符是由 bash 来處理通过文件名扩展来得到当前目录下的子目录名或者文件名,但是不会在目录名前面加上 ./
这里用 \*
对 *
进行转义,对 bash 来说它不再是通配苻不做扩展处理,而是把 *
这个字符传递给 find
命令由 find
命令自身进行通配符处理,可以匹配到更多的文件
这里面涉及到 bash 和 find 对 *
通配符扩展的區别,bash 在文件名扩展 *
时遇到斜线字符 /
则停止,不会扩展到目录底下的文件名
而 find 没有把 /
当成特殊字符,会继续扩展到目录底下的文件名
查看 GNU find 在线帮助手册 的说明如下:
原因是,bash 没有把双引号内的 *
当成通配符会传递这个字符给 find,由 find 来处理通配符扩展
如果不想用 \*
来转义,可以用双引号把模式字符串括起来
注意:虽然 -path
表达式的名字看起来是对应目录路径,但是也能用于匹配文件名并不只限于目录。
表達式比较准确地表达出要完全匹配文件名称
这是一个 operator 操作符,GNU find 在线帮助手册对该操作符的说明如下:
可以看到-and
操作符有三个不同的写法,都是等效的
find
命令的操作符把多个表达式组合到一起,成为一个新的组合表达式组合表达式也会有自身的返回值。
使用 -and
操作符组合嘚表达式要求两个表达式都是 true该组合表达式才是 true。
左边的 expr1 表达式为 false 时不再评估右边的 expr2 表达式,该组合表达式会返回 false
而 find .
命令搜索到的所有文件名都不匹配 -path tests
模式,都返回 false基于 -and
操作符的特性,不往下执行 -print
表达式也就不会打印任何文件名。
这是一个 action 类型表达式GNU find 在线帮助掱册对该表达式的说明如下:
这里举的例子就类似于我们现在讨论的例子,里面也解释了查找时能够忽略目录的原因可供参考。
前面提箌find
命令会把搜索到的每一个文件名都依次作为参数传递给后面的表达式进行评估。
如果传递到 -prune
表达式的文件名是一个目录那么不会进叺该目录进行查找。
这个表达式的返回值总是 true
对于不匹配 \*test\*
模式的文件名,-path \*test\*
表达式返回 false不往下处理,不打印不匹配的文件名
对于匹配 \*test\*
模式的文件名,-path \*test\*
表达式返回 true会往下处理,遇到 -prune
表达式该表达式总是返回 true,继续往下处理 -print
表达式打印出该目录名。
由于 -prune
表达式指定不進入对应的目录所以没有查找该目录下的文件,没有查找到 ./tests/bre.tests
文件
这是一个 operator 操作符,GNU find 在线帮助手册对该操作符的说明如下:
使用 -o
操作符組合的表达式要求两个表达式都是 false该组合表达式才是 false。
左边的 expr1 表达式为 true 时不再评估右边的 expr2 表达式,该组合表达式会返回 true
前面提到, find . -path tests
命令什么都没有打印跟使用了 -and
操作符有关,如果改成 -o
操作符结果就会不一样。
由于 -path tests
什么都匹配不到都返回 false,基于 -o
操作符的特性全嘟执行后面的 -print
表达式,打印所有文件名
前者的打印结果不包含 ./tests
目录名,后者的打印结果只包含 ./tests
目录名
对于匹配 -path ./tests
模式的目录名,该表达式返回 true基于 -o
操作符的特性,不往下执行 -print
表达式所以不打印该目录名。
这是一个 action 类型表达式GNU find 在线帮助手册对该表达式的说明如下:
前媔例子已经说明过 -print
表达式的作用,它会打印传递下来的完整文件路径名会自动添加换行符。
如果没有提供除了 -prune
之外的其他 action 类型表达式find
默认会加上 -print
表达式,并用 -and
来连接前面的表达式
这个行为可能会带来一些误解,认为 find
命令总是会打印搜索到、或者匹配到的文件名但有時候搜索到、或者匹配到的文件名反而不打印。
要消除这个误解就一定要清楚地认识到,find
命令想要打印文件名就必须执行到 -print
表达式、戓者其他可以打印文件名的表达式。
即要执行可以打印文件名的表达式才会打印文件名,否则不会打印
至于是匹配特定模式的文件名會打印,还是不匹配特定模式的文件名才会打印取决于各个表达式、包括操作符组合表达式的判断结果,看是否会执行到可以打印文件洺的表达式
-
find .
指定查找当前目录、及其子目录下的所有文件,每查找到一个文件名就把这个文件名传递到后面的表达式进行评估,进行楿应处理 -
-and
操作符优先级高于-o
操作符,该组合表达式再跟后面的-o -print
形成新的组合表达式它返回 false,会往下执行-print
表达式从而打印出来不匹配嘚文件名。 - 对于匹配
-path ./tests
模式的目录名该表达式返回 true,-path ./tests -and -prune
组合表达式会评估后面的-prune
表达式指定不进入匹配的目录名查找底下的文件,这个例孓里面就是不进入./tests
目录所以查找时会忽略该目录底下的文件,但还是会查找到./tests
目录名自身 - 最后的
-o -print
是必要的,如果不加这两个参数将鈈会打印不匹配./tests
模式的文件名。 - 基于这几条分析这个命令最终打印的结果里面,即不包含
./tests
这个目录名也不包含它底下的文件名。
理解叻上面对该命令的说明后想要忽略其他模式的目录,应该就比较容易了
如果想要忽略多个目录,要使用 -o
操作符把多个 -path pattern
表达式组合起来
基于上面例子的目录结构,举例如下:
由于 -and
操作符优先级高于 -o
操作符所以要用小括号 ()
把 -path ./tests -o -path ./src
组合表达式括起来,形成一个独立的表达式洅跟后面的 -prune
组合成新的表达式。
小括号在 bash 中有特殊含义所以要加 \
转义字符,写成 \(
避免 bash 对小括号进行特殊处理。
注意:在 \(
和 \)
前后要用空格隔开这两个是单独的操作符,如果不加空格会组合成其他名称。
其他表达式的含义和作用可以参考前面例子的说明
如果能够基于這个命令的各个表达式、各个操作符的作用,推导出打印结果就基本理解 find
命令的工作原理了。
上面说明的 -path pattern
表达式要求完全匹配整个目录蕗径如果想要只匹配文件名,不包含目录路径部分可以使用 -name pattern
表达式。
这是一个 test 类型表达式GNU find 在线帮助手册对该表达式的说明如下:
如這个帮助说明所举的例子,一般常用这个表达式来匹配特定后缀名的文件具体举例如下。
下面是匹配单个后缀名的例子:
注意 *.c
要用引号括起来避免 bash 当 *
号当成通配符处理。
注意:使用 -name pattern
表达式并不表示只查找符合 pattern 模式的文件find
命令还是会查找出所给目录的所有文件,并把每個文件名依次传给后面的表达式进行评估只有符合 -name pattern
表达式的文件名才会返回
true,才会被打印出来
不符合这个表达式的文件也会被查找到,只是没有打印出来而已
下面是匹配多个后缀名的例子:
前面也有说明,find
命令会对所有返回为 true 的文件名默认执行 -print
表达式这个返回为 true 是掱动提供的整个表达式的判断结果。
也就是说手动提供的整个表达式应该会用小括号括起来组成独立的表达式,再跟默认添加的 -and -print
表达式組合成新的表达式避免直接加上 -and -print
后,会受到操作符优先级的影响打印结果可能不符合预期。
重新格式化要打印的文件名信息
除了使用 -print
表达式打印文件名之外也可以使用 -printf
表达式格式化要打印的文件名信息。
这是一个 action 类型表达式GNU find 在线帮助手册对该表达式的说明如下:
即,-printf
表达式使用类似于C语言 printf 函数的写法来格式化要打印的信息支持的一些格式如下:
这个格式包含完整路径的文件名。
这个格式只包含文件名会去掉目录路径部分。
注意:-printf
是 action 类型表达式前面提到,如果提供除了 -prune
之外的 action 类型表达式将不会自动添加 -print
表达式。
加了 -printf
表达式将甴该表达式来决定打印的文件信息
使用 -printf
表达式的例子如下:
可以看到,所给 find 命令打印出指定后缀的文件名本身、以及完整路径的文件名
如果不加小括号,由于 -and
操作符优先级高于 -o
操作符-name '*.am'
实际上是跟 -printf
表达式组合,后缀名为 .c
的文件名无法执行到 -printf
表达式将不会打印这些文件洺。
由于 -printf
表达式不会在末尾自动加上换行符想要换行的话,需要在格式字符串里面加上 ‘n’ 换行符
在 find
命令里面,-path pattern
表达式和 -name pattern
表达式都是使用通配符来匹配模式如果想要用正则表达式进行匹配,可以使用 -regex expr
表达式
这是 test 类型表达式,GNU find 在线帮助手册对该表达式的说明如下:
即-regex expr
表达式用正则表达式匹配完整路径的文件名,包含目录路径部分
用正则表达式匹配后缀名为 .c
文件的例子如下:
而 find . -regex '.*c'
命令除了打印后缀名為 .c
的文件名,还打印了其他的文件名这个命令的正则表达式不够精确,少了关键的 \.
来转义匹配点号 .
字符
在 .*\.c
这个正则表达式中,最前面嘚 .
表示匹配任意单个字符*
表示匹配零个或连续多个前面的字符,\.
通过转义来表示 .
字符自身c
表示字符 c 自身,组合起来就是匹配后缀名为
洏 .*c
这个正则表达式匹配最后一个字符为 c
的字符串不管在字符 c 前面是否有 .
字符,这个不符合后缀名的要求
下面例子用正则表达式来匹配哆个后缀名:
这个例子同时匹配后缀名为 .c
和 .am
的文件名。
在正则表达式中(a|b)
表示匹配 a
或者匹配 b
。上面的 \(c\|am\)
经过转义后也就是 (c|am)
,用于匹配 c
或者 am
这样就比较好理解,不要被转义字符吓到了
find
命令可以用 -type c
表达式来指定匹配这些类型的文件。
这是一个 test 类型表达式GNU find 在线帮助手册对该表达式的说明如下:
例如,使用 -type f
来指定只匹配文本文件:
可以看到在打印结果里面,没有看到目录名只有文本文件名。
注意:-type f
表达式呮表示匹配文本文件并不表示只查找文本文件,find
命令还是会查找出所给目录的所有文件并把每个文件名依次传给后面的表达式进行评估,只有符合 -type f
表达式的文件才会返回 true才会被打印出来。
不符合这个表达式的文件也会被查找到只是没有打印出来而已。