find:条件组合、-exec 两种写法与性能实战
知道 fd 语法更直觉、速度更快,但 SSH 到一台没装第三方工具的服务器上,还是得翻出记忆里生锈的 find 语法。条件和动作到底谁写前面、-mtime 和 -atime 哪个是修改时间、-exec 后面跟的是 \; 还是 +——每次都要重新查一遍。
find 是所有 Linux 发行版都自带的工具,这篇把它自己的用法讲清楚。
这是 Linux 常见命令系列第三篇详细教程,承接 Linux 常见命令与工具地图、chmod/chown、grep。如果你的环境能装第三方工具,fd 语法比 find 直觉得多——但装不上的场合,还得靠这篇。
基本语法:起始路径、条件、动作三段式
find 命令的结构是固定的三段:
1 | find 起始路径 条件 动作 |
比如 find . -name "*.log" -print:. 是起始路径(从当前目录开始找),-name "*.log" 是条件(文件名匹配这个模式),-print 是动作(把匹配到的路径打印出来)。
新手最容易搞混的地方是把条件和动作当成一回事。-name、-type、-mtime 这些是条件,负责”筛选出哪些文件符合要求”;-print、-delete、-exec 这些是动作,负责”对筛出来的文件做什么”。不写动作时 find 默认执行 -print,所以 find . -name "*.log" 和 find . -name "*.log" -print 效果一样——但显式写上 -print,尤其是命令里还有别的动作时,意图更清楚,不容易和条件搞混。
按名字和类型找:-name、-iname、-type
1 | find . -name "*.log" |
按时间找:-mtime、-atime、-ctime 的区别
三个时间条件容易搞混,先说清楚各自对应什么:
| 参数 | 对应的时间戳 | 什么情况下会变 |
|---|---|---|
-mtime(modify time) |
内容修改时间 | 文件内容被写入/修改 |
-atime(access time) |
访问时间 | 文件被读取(有些文件系统挂载时禁用了 atime 更新,出于性能考虑) |
-ctime(change time) |
状态变更时间 | 文件的元数据变了——权限、属主、链接数,不一定是内容变了 |
日常排查用得最多的是 -mtime:
1 | find . -mtime -7 |
按大小找:-size 的符号前缀
-size 的符号方向和 -mtime 是同一套逻辑,但因为单位是大小不是时间,容易理解反:
1 | find . -size +100M |
单位后缀:c 是字节,k 是 KB,M 是 MB,G 是 GB,不写单位默认是 512 字节的块数(历史遗留,容易踩坑,写 size 条件时最好总是带上单位)。
逻辑组合:-and、-or、-not 与括号分组
多个条件之间默认是”且”的关系,-and 可以省略:
1 | find . -type f -name "*.go" |
-exec 的两种写法:\; 和 +,性能差在哪
这是 find 里最容易被忽略、但影响最大的细节。两种写法看起来只差一个符号,行为和性能却完全不同:
1 | find . -name "*.log" -exec gzip {} \; |
为什么性能差这么多:\; 结尾时,找到 1000 个文件,就要 fork 1000 次子进程、启动 1000 次 gzip——每次进程创建都有实打实的系统开销(内存分配、内核调度),文件越多,这个开销累加得越明显。+ 结尾时,find 会把匹配到的文件路径尽量攒在一起,打包成尽可能少的调用次数,具体能攒多少受系统的 ARG_MAX(单次命令调用能传递的参数总长度上限)约束——Linux 上典型值是 2097152 字节(约 2MB,getconf ARG_MAX 能查到本机的实际值)。假设文件路径平均 50 字节,2MB 够装下四万多个路径,1000 个文件的路径总长度远小于这个上限,实际可能一次调用就打包完了,进程创建次数从”文件数”级别降到”个位数”,量级差出去很多。
日常写脚本的经验规则:只要命令支持一次接收多个文件路径(大多数命令都支持,比如 rm、gzip、chmod),优先用 +;只有命令一次只能处理一个文件、或者每个文件需要单独判断结果时,才用 \;。
1 | find . -name "*.tmp" -exec rm {} + |
-delete:比 -exec rm 更安全高效
需要删除匹配到的文件时,find 自带的 -delete 动作比 -exec rm {} + 更直接——不用再额外 fork 一个 rm 进程:
1 | find . -name "*.tmp" -delete |
用之前一定要先确认范围:把 -delete 换成 -print,跑一遍看看到底会命中哪些文件,确认没问题了再换回 -delete——find 的条件写错很容易命中比预期更大的范围,删除操作又无法撤销:
1 | find . -name "*.tmp" -print |
两个常见坑:文件名带空格、通配符被 shell 提前展开
1 | find . -name "*.txt" -print0 | xargs -0 rm |
find 常用参数速查表
| 参数 | 作用 |
|---|---|
-name PATTERN |
按文件名匹配,区分大小写 |
-iname PATTERN |
按文件名匹配,不区分大小写 |
-type f / d / l |
只要普通文件 / 目录 / 符号链接 |
-maxdepth N |
最多递归 N 层 |
-mindepth N |
至少从第 N 层开始算 |
-mtime ±N |
内容修改时间在 N 天以内(-)/ 以前(+)/ 精确 N 天(不带符号) |
-atime ±N |
访问时间,符号规则同 -mtime |
-ctime ±N |
元数据(权限/属主)变更时间,符号规则同 -mtime |
-newer FILE |
修改时间比 FILE 更新 |
-size ±N[ckMG] |
文件大小,符号规则同 -mtime,单位 c/k/M/G |
-and |
且,默认关系,通常省略 |
-or |
或,必须显式写 |
-not |
非,排除条件 |
\( ... \) |
括号分组,圆括号需转义 |
-exec CMD {} \; |
对每个匹配文件单独执行一次命令 |
-exec CMD {} + |
尽量批量打包,减少命令调用次数 |
-delete |
直接删除匹配到的文件 |
-print0 |
用 null 字符分隔输出,配合 xargs -0 处理带空格的文件名 |
find 之外
装得上第三方工具的话,fd 默认行为更符合直觉(自动跳过 .gitignore、语法更接近自然语言),日常开发优先用它,find 留给装不上工具的场合。Linux 命令系列下一篇计划写 rsync 或 tar——都是比 find 参数量级还大的命令,增量同步、归档压缩各有一堆细节值得展开。





