find 命令是 Linux 上查找文件的神器,要想用好命令行,find 是必须掌握的命令之一。
基本语法
find 命令的基本语法如下:
find [path] [expression]
默认 path 为当前目录;默认 expression 是 -print
,因此单单的 find
命令,打印当前目录下的所有文件。
expression 可以包含的操作有:operators, options, tests, actions。接下来一一介绍。
operator
不指定的话,默认是 -and
与 | EXPR1 -a EXPR2 或者 EXPR1 -and EXPR2 |
---|---|
或 | EXPR1 -o EXPR2 或者 EXPR1 -or EXPR2 |
非 | ! EXPR 或者 -not EXPR |
option
在其它表达式之前使用,常用的有:
-maxdepth LEVELS | 指定目录的最大深度 |
---|---|
-mindepth LEVELS | 指定目录的最小深度 |
-depth | 从目录的最深层开始查找 |
test
下面的表达式,可以用来过滤文件或目录,判断是否匹配指定的操作。
参数 | 作用 |
---|---|
-name PATTERN | 匹配名称 |
-iname PATTERN | 匹配名称忽略大小写 |
-empty | 匹配空文件 |
-perm [-/]MODE | 匹配权限 |
-user NAME | 匹配文件的所有者 |
-group NAME | 匹配文件的所属组 |
-gid N | 匹配组 ID |
-nouser | 匹配无所有者的文件 |
-nogroup | 匹配无所有组的文件 |
-atime N | 匹配文件的访问时间(单位是天)(N 可以是 +N,可以是 -N,也可以是N)(access time) |
-ctime N | 匹配文件的状态改变时间(change time) |
-mtime N | 匹配文件的修改时间(modify time) |
-amin N | 匹配文件的访问时间(单位是分钟) |
-cmin N | 匹配文件的状态改变时间 |
-mmin N | 匹配温婉的访问时间 |
-anewer FILE | 匹配 access time 比 FILE 新的文件 |
-cnewer FILE | 匹配 change time 比 FILE 新的文件 |
-newer FILE | 匹配比文件 FILE 新的文件 |
-type b/c/d/p/f/l/s/D | 匹配文件类型 |
-size N | 匹配文件的大小 |
-path PATTERN | 匹配指定路径,配合 -prune 参数排除指定目录 |
-executable | 匹配可执行文件 |
-readable | 匹配可读文件 |
-writable | 匹配可写文件 |
action
action 可以指定对匹配文件的操作:
-delete | 删除查找出来的文件 |
---|---|
打印查找出来的文件(为默认操作) | |
-exec COMMAND {} \; | 使用命令进一步处理搜索结果(最后的 \ 是对 ; 转义) |
-prune | 如果是目录的话,不遍历 |
-ok | 和 -exec 作用相同,但是在执行命令之前,会让用户确定是否执行 |
基本使用方法
默认情况下,find 命令会对文件夹进行递归查找。如果不指定目录,默认查找当前文件夹。
# 列出当前目录以及子目录下的所有文件
$ find
# 等同于
$ find .
# 也等同于
$ find . -print
# 列出指定目录下的文件
$ find ./test
# 查找指定名称的文件
$ find ./test -name "1.txt"
# 如果不指定目录,find 默认查找当前文件夹
$ find -name "1.txt"
# 使用通配符匹配文件名称
$ find ./test -name "*.txt"
# 忽略大小写使用 iname
$ find ./test -iname "*.txt"
# 在多个目录中查找
$ find ./test ./temp -name "*.txt"
# 查找不是 txt 的文件
$ find . -not -name "*.txt"
# 等同于
$ find . ! -name "*.txt"
使用路径匹配
# 查找 controller 路径下 Sys 开头的文件
$ find . -path "*/controller/*" -name "Sys*"
# 不查找 C 目录
$ find . ! -path "./C/*" -name "*.c"
使用多个条件查找
默认情况下,find 命令使用 AND 操作符连接多个条件,可以指定使用 OR 操作符。
# 查找以 test 开头但不是 txt 的文件
$ find . -name "test*" ! -name "*.txt"
# 查找 txt 文件或者 java 文件
$ find . -name "*.txt" -o -name "*.java"
使用圆括号可以将多个表达式结合在一起,但是圆括号在命令中有特殊的车含义,所以需要使用 \ 来进行转义。
忽略目录
# 不遍历当前目录
$ find . -prune
.
# 忽略 /data/temp 目录(当 -path "/data/temp" 为真时,执行 -prune,否则执行 -print)
$ find /data -path "/data/temp" -prune -o -print
# 忽略多个目录
$ find /data \( -path /data/temp -o -path /data/test \) -prune -o -print
# 相当于
$ find /data -type d \( -name temp -o -name test \) -prune -o -print
限制查找的深度
# 限制目录最大深度是 1,也就是当前文件夹
$ find . -maxdepth 1 -name "*.txt"
# 限制目录的最小深度是 2
$ find . -mindepth 2 -name "*.txt"
# 相当于 tree -L 1
$ find . -maxdepth 1 -type d
使用指定类型的文件
使用 -type
可以指定要查找文件的类型。
# 列出当前目录下的目录
$ find -type d
# 列出当前目录下的文件
$ find -type f
# 查找以 abc 开头的文件
$ find ./test -type f -name "abc*"
其它查找方式
查找空文件和目录
# 查找空文件
$ find /tmp -type f -empty
# 查找空目录
$ find /tmp -type d -empty
基于文件大小查找
# 查找指定大小的文件
$ find . -size 50M
# 查找大小在一定范围的文件
$ find . -size +50M -size -100M
基于文件拥有者和用户组查找
# 查找当前目录下,属于 flykyle 的文件
$ find . -user flykyle
# 查找当前目录下,属于 developer 用户组的文件
$ find . -group developer
# 查找没有对应任何用户的文件(文件的属主账号已经被删除)
$ chown 555 test.txt
$ find . -nouser
# 查找没有对应任何组的文件
$ chown .555 test.txt
$ find . -nogroup
基于文件权限查找
# 查找 777 权限的文件
$ find . -type f -perm 0777
# 查找不是 777 权限的文件
$ find . -type f ! -perm 777
# 查找权限是 551,并且设置了 Sticky Bit 的文件
$ find . -perm 1551
# 查找权限是 644,并且设置了 SGID Bit 的文件
$ find / -perm 2644
# 查找设置了 SUID 的文件
$ find / -perm /u=s
# 查找设置了 SGID 的文件
$ find / -perm /g=s
# 查找只读文件
$ find / -perm /u=r
# 查找可执行文件
# 由于权限不足,某些目录会拒接访问。命令中的 2>/dev/null 正是用于清除输出中的错误访问结果
$ find /bin -perm /a=x 2>/dev/null
基于时间和日期查找
# 查找过去的第 3 天修改的文件
$ find / -mtime 3
# 查找指定时间范围内修改的文件
$ find / -mtime +1 -mtime -7
# 查找过去的 10 天内被访问过的文件
$ find / -atime -10
# 查找过去的 N 分钟内状态发生改变的文件
$ find ./test -cmin -60
# 查找过去的 1 小时内被修改过内容的文件
$ find ./test -mmin -60
# 查找过去的 1 小时内被访问过的文件
$ find ./test -amin -60
# 查找更改时间比 file1 新但比 file2 旧的文件
$ find . -newer file1.txt ! -newer file2.txt
查找后进一步操作
-exec COMMAND {} \;
操作,其中的 {}
表示 find 命令搜索出的每一个文件。
# 删除 temp 文件
$ find . -name "*.temp" -delete
# 相当于
$ find . -name "*.temp" -exec rm -f {} \;
# 列出文件的详细信息
$ find . -name "*.txt" -ls
# 相当于
$ find . -name "*.txt" -exec ls -l {} \;
# 相当于
$ find . -name "*.txt" | xargs ls -l
# 查找权限是 777 的目录,然后修改权限为 755
$ find /home -type d -perm 777 -print -exec chmod 755 {} \;
# 删除更改时间在 14 天前的日志,-ok 需要用户确认是否删除
$ find /var/log -name "*.log" -mtime +14 -ok rm {} \;
配合 xargs 使用
xargs 是一个命令,可以向其它命令传递参数。
# 列出 txt 文件
$ find . -name "*.txt" | xargs ls -l
# 删除文件
# xargs 的 -p 选项会让用户确认是否执行后面的命来
$ find . -name "*.temp" | xargs -p rm -f
xargs 和 exec 的区别
-exec | xargs |
---|---|
将查找的结果文件名逐个传递给后面的命令执行,如果文件比较多,执行效率比较低 | 将查找的结果一次性传给后面的命令执行,执行效率高,可以使用 -n 参数控制一次传递文件的个数 |
文件名有空格等特殊字符也照常处理 | 文件名有空格,需要采用特殊的方式( find . -name "*.txt" -print0 | xargs -0 ls -lh ) |
# 可以看到 -exec 对每个文件都执行了 echo 命令
$ find . -type f -exec echo file-is: {} \;
file-is: ./test/2.java
file-is: ./test/abc.java
file-is: ./test/1.txt
file-is: ./test/abc.txt
file-is: ./test.tar.gz
file-is: ./file1.txt
file-is: ./file3.txt
file-is: ./file2.txt
file-is: ./xx/file10.txt
file-is: ./temp/2.java
# 可以看到使用 xargs,echo 命令只执行了一次
$ find . -type f | xargs echo file-is:
file-is: ./test/2.java ./test/abc.java ./test/1.txt ./test/abc.txt ./test.tar.gz ./file1.txt ./file3.txt ./file2.txt ./xx/file10.txt ./temp/2.java
# 使用 -n 指定传递参数的个数
$ find . -type f | xargs -n 3 echo file-is:
file-is: ./test/2.java ./test/abc.java ./test/1.txt
file-is: ./test/abc.txt ./test.tar.gz ./file1.txt
file-is: ./file3.txt ./file2.txt ./xx/file10.txt
file-is: ./temp/2.java
# 创建一个名称带有空格的文件
$ touch "xiao can.txt"
# 使用 -exec 可以查看
$ find . -name "xiao*" -exec ls -l {} \;
-rw-r--r-- 1 xiaocan xiaocan 0 Dec 8 12:01 './xiao can.txt'
# 正常使用 xargs 会报错
$ find . -name "xiao*" | xargs ls -l
ls: cannot access './xiao': No such file or directory
ls: cannot access 'can.txt': No such file or directory
# 特殊处理
$ find . -name "xiao*" -print0 | xargs -0 ls -l
-rw-r--r-- 1 xiaocan xiaocan 0 Dec 8 12:01 './xiao can.txt'
练习
- 把 /script 目录及其子目录下的 .sh 文件中的 “./service.log” 替换为 “./service-test.log”
# 要用到的 sed 命令
# sed -i "s#./service.log#./service-test.log#g"
# find + exec 方法
$ find /script -name "*.sh" -exec sed -i "s#./service.log#./service-test.log#g" {} \;
# find + xargs 防止
$ find /script -name "*.sh" -exec | xargs sed -i "s#./service.log#./service-test.log#g"
# 使用反引号 ``(命令中如果有反引号,优先执行反引号中的命令)
$ sed -i "s#./service.log#./service-test.log#g" `find /script -name "*.sh"`
- 删除一个目录下的所有文件,但保留指定文件
# 创建10个文件
$ touch file{1..10}.txt
# 使用 find + xargs 删除文件
$ find . -type f ! -name "file10.txt" | xargs rm -f
# 使用 find + exec 删除
$ find . -type f ! -name "file10.txt" -exec rm -f {} \;
- 将找到的文件移到指定的位置
# 将 14 天的前的日志,移到 /var/oldlog 文件夹
# 使用 xargs -i 参数,使得 {} 代表 find 查找到的文件
$ find /var/log -name "*.log" -mtime +14 | xargs -i mv {} /var/oldlog
# 或者
# mv 的 -t 选项,可以颠倒源和目标
$ find /var/log -name "*.log" -mtime +14 | xargs mv -t /var/oldlog
# 使用反引号
$ mv `find /var/log -name "*.log" -mtime +14` /var/oldlog