find 命令是在指定目录下搜索文件,并向标准输出打印文件的信息。例如
find work/
find 命令会打印 work 目录下的所有文件的信息。
在类Unix系统中,一切皆文件,因此这里的文件指普通文件,目录,符号链接等等。
语法
find 最常用的语法如下
find [directory...] [expression]
directory 表示要搜索的目录,可以有多个目录,expression 是表达式。例如
find work/ -name test.c
work 就是要搜索的目录,-name test.c
是表达式。
表达式
find 表达式,由以下几个元素( primaries )中的任意几个组成
- 选项(Options)。选项会影响全局的行为,例如 -regextype 指定使用的正则表达式。
- 测试(Tests) : 判断文件的属性,返回 true 或 false。例如,通过 -name 判断文件名。
- 行为(Actions) : 用于执行某个动作,返回 true 或 false。例如删除匹配的文件。
- 操作符(Operators) : 用于给测试和行为添加逻辑操作符,也就是与、或、非。
选项
选项会影响全局的行为,这里介绍几个常用的选项。
-regextype
-regextype 选项用于指定正则表达式的类型,值有 emacs 、posix-awk 、posix-basic 、 posix-egrep 、 posix-extended。find 默认使用 GNU emacs 正则表达式。
正则表达式类型五花八门,我们不可能去记住所有,但是 POSIX 正则表达式却是通用且可移植的,因为如果一旦要在 find 命令中使用正则表达式,那么我们把 -regextype 设置为 posix-basic 或 posix-extended。
-depth
-depth 选项表示表示优先处理目录下的内容,再处理目录本身。
注意 -depth 并不是表示处理目录的深度。
-mindepth, -maxdepth
-mindepth / -maxdepth 都是基于 find 搜索的起始目录,指定搜索的最小 / 最大的目录深度。
$ find out -maxdepth 4 -name userdata.img
out/target/product/msm8937_32/userdata.img
在 out 目录下有两个 userdata.img ,但是我们只需要其中一个,因此可以指定搜索的最大的目录深度。
这里有个小支持,目标文件在第几层目录下,-maxdepth 就指定相应的数字即可。
测试
find 表达式中的测试,是针对文件属性的测试,这里列出常用的测试项。
-name
-name pattern 对基本文件名进行匹配, pattern 使用通配符来匹配文件名。
可以参考GNU官网解释的 通配符 。
下面的命令是在当前目录下搜索基本文件名是 test.c 的文件。
find . -name test.c
下面的命令是搜索基本文件名以 .java 结尾的文件
find . -name \*.java
注意,为了防止 shell 扩展,需要把通配符( 这里指星号 )进行转义或者添加引号。
-iname 是 -name 忽略大小写的版本。
-path
-path pattern 把文件的全路径名进行匹配,参数 pattern 使用通配符进行匹配。
由于 -path 匹配的是带有全路径的文件名,而不是匹配目录,因此在 pattern 字符串最后使用斜线( / )是无法匹配任何文件的。
下面的命令是在 work 目录下找到 test 的文件。
find work -path work/test
这个 test 文件可以是文件,也可能是目录。
-regex
-regex pattern 与 -path pattern 的相似之处是,它们都使用文件的全路径进行匹配,但是 -regex 的 pattern 使用的是正则表达式匹配,而 -path 的 pattern 使用的是通配符进行匹配。
正如前面所说,find 命令默认使用 GNU emacs 正则表达式,但是可以通过 -regextype 设置自己熟悉的正则表达式,通常我们使用可移植的 POSIX 正则表达式。
find work/ -regextype posix-basic -regex '.*\.c'
这个命令是在 work 目录下搜索 .c 结尾的文件,通过 -regextype 设置 POSIX 基本正则表达式,通过 -regex 设置正则表达式。
GNU官网上有关于所有 正则表达式 的简单介绍,但是是高度概括。
-type
-type 用来测试文件类型,例如 -type f 测试文件是否是普通文件,-type d 测试文件是否是目录。
比较下面两个命令的异同
find . -name \*.bak -type f
find . -type f -name \*.bak
两个命令都是找出基本名以 .bak 结尾的的普通文件,但是性能有所差异。
第一个命令,是先找出基本名以 .bak 结尾文件,再判断文件类型是否为普通文件。
第二个命令,是先判断文件类型是否为普通文件,再找出基本名以 .bak 结尾文件。
类型判断会调用stat()
函数,第一个命令只对 .bak 结尾的文件调用这个函数,而第二个命令会为所有的文件调用这个函数。
如果一个目录下有大量的文件和目录,那么这个性能差异就能体现出来。
行为
find 表达式中的行为可以对匹配的文件进行某种操作。
-print 会文件路径打印到标准输出,也就是屏幕上。
对于 -print ,我们尤其要注意的一点是
-print 行为默认会在匹配的文件上执行,除非表达式包含除了 -prune 和 -quit 行为以外的其它行为。
这一点是理解复杂的 find 命令的关键。
fprint file 会把结果输出到文件 file 中,而不是标准输出。
-print0
-print0 也是把文件路径打印到标准输出,但是输出结果以空字符(ASCII码为0)分隔,而-print
的输出结果是以换行符分隔。
-print 的输出结果如下
$ find . -name \*.c
./hello.c
./test.c
虽然省略了-print 行为,但是还记得刚才说过的吗,系统默认会使用 -print 对匹配的文件进行打印。
-print0 输出结果如下
$ find . -name \*.c -print0
./hello.c./test.c
可以看到两个名子连接到了一起,其中它们是以空字符分隔的,只是屏幕上没有显示出来而已。
那么 -print0 有什么用呢?
类 Unix 系统允许除了空字符( ASCII码为0 )和斜线( / )以外的任意字符出现在文件名中。也就是说文件名中可能出现空白字符,例如空格,制表符,换行符。
find 命令如果使用 -print 行为,那么生成的结果是以换行符分隔的,然而如果此时文件名中包含换行符,并且 find 结果管道到另外一个程序,那么这个程序就无法正确处理。
为了解决这个问题,GNU 给 find 命令增加了 -print0 行为,它把输出的结果以空字符(不是空白字符,ASCII码为0)进行分隔。对于接收 find 结果的程序呢,它也需要指定分隔符。例如 xargs 可以通过 -0 选项把分隔字符指定为空字符。如此一来,就可以正确处理文件名中有换行符的情况。
find . -name \*.bak -print0 | xargs -0 /bin/rm
-ls
-ls 行为使用 ls -dils 打印结果到标准输出。
-fls file 会把结果输出到 file 中。
-prune
如果使用了 -prune 行为,并且处理的是目录,那么这个行为将会返回 true,并且 find 命令不会进入这个目录搜索。
-prune 通常用来跳过某个目录,例如
find . -path './src/emacs' -prune -o -print
我们来理解一下这个命令,因为我刚开始也疑惑了好久,首先表达式中有了 -print 行为,因此系统不会再使用默认的-print 打印匹配的文件。
然后,如果 -path './src/emacs' -prune
为 true,代表目录为 emacs,并且此时不会进入这个目录进行搜索。由于此时不会执行后面的 -o -print
,因此就算匹配了 emacs 目录,也不会打印这个目录以及目录下的内容。
如果 -path './src/emacs' -prune
为 false,那么就证明不是 emacs 目录,那么就会执行 -o
后面的 -print
行为。
因此,这个命令的整体效果就是跳过 emacs 目录,并打印其它目录以及目录下的内容。
如果我们还要打印 emacs 目录,只需要在后面加上一个 -print
即可
find . -path '.src/emacs' -prune -print -o -print
或者
find . -path '.src/emacs' -prune , -print
逗号也是 find 表达式中的操作符,它会执行前,后两个动作
-delete
-delete
删除匹配的文件或目录。
find . -name \*.bak -delete
这个命令会删除当前目录下以.bak
结尾的文件。
1.删除动作永远是危险的,因为不可恢复,所以在删除之前,最好查看一下要删除哪些文件。
2.-depth
和-prune
,这两个行为是冲突的,因为-depth表示先处理目录下内容,再处理目录,而-prune不会进入目录。-delete隐式包含-depth选项,因此-delete不能与-prune一起使用。
-exec, -execdir
-exec command {} ; 对每一个匹配的文件执行 command 命令。
其中 {} 会被替换为正在处理的文件名,; 表示命令结束。由于 {} 和 ; 对 shell 来说有特殊含义,因此需要对它们转义或者加引号。
例如下面命令会把 work 目录下的所有c文件复制到 dest 目录下。
find work/ -name \*.c -exec cp '{}' dest/ ';'
-exec command {} ; 有一个小小的性能问题,那就是针对每一个文件,都会调用一次命令。而 -exec command {} + 会把所有的文件名添加到命令的末尾,只执行一次命令。
因此上面的命令可以更改如下
find work/ -name \*.c -exec cp -t dest/ '{}' +
这个命令有几点需要注意
- 加号 ( + ) 不会被 shell 特殊对待,因此不用转义或者加引号。
- {} + 作为一个整体,{} 被替换为文件列表,+ 代表命令结束,中间不能添加任何字符。因此 cp 命令必须把目标目录以 -t dest 的形式,在 {} + 之前添加。
出于安全考虑,可以把 -exec 替换为 -execdir。
-ok ,-okdir
-ok command {} ; 的功能与 -exec command {} ; 相似,都会为每个匹配的文件执行一次命令,但是 -ok command {} ; 会询问用户是否执行。
出于安全考虑,可以把 -okdir command {} ; 替换为-ok command {}; 。
但是令人奇怪的时,没有 -ok command {} + 或 -okdir command {} + 行为,这样就不能询问用户是否对所有文件执行一次命令,但是我们可以通过 xargs 的 -p / --interactive 选项来完成这种工作。
find work/ -name '*.c' | xargs -p ls -l
这个命令会先查询 work 目录所有 c 文件,然后询问用户是否对这些文件执行一次 ls -l 命令。
-quit
-quit
行为表示立即退出。
例如在某个目录下,可能有几个相同的文件,但是我们只需要其中一个,可以使用如下命令
find work/ -name hello.c -quit
操作符
以下列出与POSIX兼容的操作符,以降序优先级排序
( expr )
: 用于提升优先级。括号对 shell 有特殊意义,因此需要转义或加引号。! expr
: 非操作,!
也需要转义或者加引号。expr1 -a expr2
: 与操作,如果省略操作符,默认也是与操作,例如expr1 expr2
。如果 expr1 返回 false,不会执行 expr2。expr1 -o expr2
: 或操作。如果 expr1 返回 true,那么不会执行 expr2。expr1 , expr2
: 表达式expr1
和expr2
都会被计算,但是整体表达式的值的返回值由expr2
决定。
结束
find 命令其实还有很多内容,在需要的时候,可以通过 man find
查询 find 手册。