Linux find 命令实战:-mtime 与 -size 语义陷阱、-exec 的 + 与 \; 性能差异、-print0 与 xargs

find 是站点运维里使用频率最高、也最容易用错的命令

服务器运维里真正每天都会用到的命令其实不多,find 一定在前三名。清理过期日志、找出最近被改动的文件、定位大文件、批量修正权限、查备份目录里哪些文件没同步过去——这些任务都落在 find 身上。但 find 也是「看起来会、实际经常踩坑」的典型:按时间筛出来的结果和想的不一样、-size +1M 漏掉了一堆大文件、-exec 后面的分号和加号写错导致命令跑了几十分钟、路径里带空格的文件在管道里被截断。

这篇文章不讲 find 的所有参数,而是把它拆成四个最容易出错的部分:筛选条件(时间、大小、权限、名字)、动作执行(-exec 的两种形态)、与 xargs 的配合、以及真正落地到站点运维的清理脚本。每一节都给出可直接复制的命令和它的真实语义。

第一部分:时间筛选的三套语法,别混着用

find 的时间条件是新手最大的困惑来源。它同时提供三套语法,语义各不相同:

-mtime 7     # 修改时间,单位「天」,24 小时为一整块
-mmin 120    # 修改时间,单位「分钟」
-newermt '2026-09-01 00:00'   # 比这个绝对时间新的文件
-newer /path/to/reference     # 比这个参考文件新的文件

先看 -mtime 最容易误解的地方。-mtime 7 并不是「最近 7 天」,而是「修改时间在 7×24 小时到 8×24 小时之间」,也就是第 8 天那一整块。真正表达「7 天以前」要写成 -mtime +7。这个细节在写清理脚本时会直接决定你删掉的是该删的还是不该删的:

# 删除 30 天之前的日志(正确)
find /var/log/myapp -name "*.log" -mtime +30 -delete
# 危险:这会删掉恰好处于第 30 个 24 小时块内的文件
find /var/log/myapp -name "*.log" -mtime 30 -delete

第二条的危险在于「一块」只有一天的量,看起来影响不大,但当你把它和别的判断组合时,语义会迅速变得难以预测。所以实践中建议:要「超过 N 天」就用 +N,要「最近 N 天内」就用 -N,永远不要用裸的 N

-mtime 更值得推荐的是 -newermt。它接受一个人类可读的绝对时间,语义直白,不需要在脑子里做乘法:

find /var/www -type f -newermt '2026-09-01' -not -newermt '2026-09-15'
find /var/www -type f -mmin -30      # 最近 30 分钟内改动的文件

第二条 -mmin -30 是排查「网站文件刚被谁改了」的利器。当页面突然出现异常内容时,用它先圈出最近半小时被动过的文件,范围立刻收窄:

find /var/www/html -type f -mmin -60 -ls

-ls 会让输出带上权限、属主、大小和时间,比默认的裸路径信息量大得多,适合做初步判断。

第二部分:-size 的单位陷阱与「按大小找大文件」

磁盘告警时最常用的一条命令是找大文件,而 -size 的单位规则经常让人以为命令失效:

-size 100c   # 100 字节
-size 100k   # 100 KB(1024 字节)
-size 100M   # 100 MB
-size 100G   # 100 GB
-size +100M  # 大于 100 MB

关键点有两个。第一,不带后缀时单位默认是 512 字节的块,不是字节。写 -size 100 表示 100 个块即 51200 字节,几乎没有人想要这个语义,所以务必显式带上 c/k/M/G。第二,find 的大小比较是「向上取整到单位」。这意味着 -size -1M(小于 1MB)实际匹配的是「取整后严格小于 1」的文件,也就是 0 字节的文件;一个 500 字节的文件会被算作 1M 而落在范围外。这个语义反直觉,写「找小于某尺寸的文件」时一定要先小心验证。

实践中更稳妥的写法是把 find 的结果交给 sort 排序,用数值而不是 find 的模式来做判断:

find /var/www -type f -size +50M -printf '%s\t%p\n' | sort -rn | head -20

-printf '%s\t%p\n' 让 find 直接输出字节数和路径,sort -rn 按数值倒序,head 取前 20 条。这条命令的输出是一张「服务器上最占空间的 20 个文件」清单,对定位磁盘占用的效率极高,而且完全没有取整语义的干扰。

第三部分:-exec 的 \; 与 + 差一个字符,性能差几十倍

这是本文最值得记住的一节。find 执行动作时有两个看起来相似、实际行为完全不同的写法:

# 形态一:反斜杠加分号,对每个文件启动一次命令
find /tmp -name "*.tmp" -exec rm -f {} \;

# 形态二:加号,把所有匹配文件一次性拼到命令末尾
find /tmp -name "*.tmp" -exec rm -f {} +

两者的区别是:\; 会为每一个匹配到的文件独立 fork 一个进程,匹配一万个文件就启动一万次 rm;+ 会把匹配结果按 ARG_MAX 上限分批,一次性传给命令,一万个文件可能只启动两三次。在文件数量大的目录上,这个差别是「几十秒」和「几分钟」的区别,在某些小内存 VPS 上甚至是「跑得动」和「OOM」的区别。

+ 有一个必须知道的限制:它要求命令的「参数列表末尾可接受多个参数」。像 rm -f {} +chmod 644 {} +tar -cf x.tar {} + 都成立,但如果命令本身的语义依赖「一次一个文件」,就不能用 +。还有一个更隐蔽的坑:{}+ 模式下必须出现在命令的最后(因为它要被拼接上去),写成 -exec cp {} /backup + 是错的,cp 需要的是 -exec cp -t /backup {} + 这种「目标目录在前、源文件批量在后」的形式。

所以一条实用的经验法则是:能批量就用 +,需要逐文件处理或者命令语义不允许批量时才用 \;。写批量权限修复时优先用 +:

find /var/www/html -type d -exec chmod 755 {} +
find /var/www/html -type f -exec chmod 644 {} +

第四部分:路径带空格时,管道会悄悄吃掉文件名

find 的输出默认是「一行一个文件路径」,用换行分隔。这在文件名不含空格时没问题,但一旦文件名里有空格,把结果管道给别的命令就会出问题:

# 错误:含空格的文件名会被拆成多个参数
find /data -name "*.jpg" | xargs rm -f

如果有个文件叫 my photo.jpg,xargs 会把它拆成 myphoto.jpg 两个参数,结果是「删不掉真文件」外加「可能误删同名的其他文件」。两种正确解法:

# 解法一:用 -print0 配合 xargs -0,用 NUL 分隔,空格不再是分隔符
find /data -name "*.jpg" -print0 | xargs -0 rm -f

# 解法二:直接用 find 自己的 -delete,完全不经过管道
find /data -name "*.jpg" -delete

第二种更彻底,因为不过管道就没有分隔符问题。但要特别注意:-delete 会隐式开启 -depth,也就是先处理子目录再处理父目录。这个副作用会让 -delete 不能和某些「需要先遍历父目录」的选项组合使用。更重要的是,-delete 的顺序决定了它的危险性——先用 -print 跑一遍确认命中列表,再换成 -delete,这是清空数据操作唯一可靠的保险:

# 第一步:先看会命中什么
find /var/log/myapp -name "*.log" -mtime +30 -print
# 第二步:确认无误后再执行
find /var/log/myapp -name "*.log" -mtime +30 -delete

落地:三个真正会用到的站点运维脚本

把上面的知识点组合成实际脚本,才算是学会了。下面三条覆盖个人站长最常遇到的需求。

需求一:清理 30 天前的日志,但保留 .gitkeep 之类的占位文件。

find /var/log/myapp -type f -name "*.log" -mtime +30 \
  -not -name ".gitkeep" -print -delete

-print -delete 同时写在一起时,find 会先打印再删除,等于在删除动作上自带审计输出。配合 cron 每天跑一次并重定向到日志文件,就得到了一份「每天删了什么」的记录。

需求二:找出最近 6 小时被修改过的网站文件,用于快速定位被篡改或误改的内容。

find /var/www/html -type f \
  \( -name "*.php" -o -name "*.js" -o -name "*.html" \) \
  -mmin -360 -printf '%TY-%Tm-%Td %TH:%TM  %p\n' | sort

这里有两个语法要点:\( ... \) 把多个 -name 组合成一组(括号必须转义,否则会被 shell 吃掉),-printf%T 系列格式符输出可读时间。-o 是逻辑或,配合括号才能表达「php 或 js 或 html」而不是被 find 的短路求值规则误解析。

需求三:批量修正权限,但区分目录和文件。

find /var/www/html -type d -exec chmod 755 {} +
find /var/www/html -type f -exec chmod 644 {} +
find /var/www/html -type f -name "*.sh" -exec chmod 750 {} +

三条命令的分工是:目录给执行位(755,否则无法进入),普通文件不给执行位(644),脚本单独给 750。这是最不容易出权限问题的组合。反过来,如果随手 chmod -R 777,就等于把整个站点目录变成了任意用户可写可执行,等于主动开门。

小结

find 的难点不在参数多,而在几个语义反直觉的地方。回顾四个关键点:第一,-mtime +7 才是「超过 7 天」,裸的 -mtime 7 是一整块 24 小时;第二,-size 不带后缀默认按 512 字节块,而且比较是向上取整的,判断大文件优先用 -printfsort -rn;第三,批量执行用 -exec ... + 而不是 \;,性能差一个数量级;第四,路径可能带空格,过管道一定配 -print0 | xargs -0,或者干脆用 -delete。最后一条最重要的纪律:任何 -delete 之前,先跑一遍 -print 看清命中列表。

Last modification:September 22nd, 2026 at 10:25 pm

Leave a Comment