前一阵帮同事排查测试服务器,两百多G的日志把根分区吃满了。同事的第一反应是用图形界面翻目录,翻半天没找到东西在哪。我跟他说,你敲一行find / -size +100M,几秒钟就能把大文件列出来。他试完发的第一句话是:“这命令也太猛了。”这就是我想写这篇东西的原因。作为小白Linux学习日记的第二篇,这次只讲一个命令:find。不要小看它,它是你以后在服务器上“找东西”最重要的武器,也是你从图形界面思维切换到命令行思维必经的一道坎。
这篇内容没有任何高深的理论,我尽量按实际使用的顺序来写:先弄明白find长什么样,再逐个拆解最常用的参数,然后配合实际场景讲操作,最后把常见的坑和排查思路一起列出来。适合完全没接触过find的新手,也适合会几个参数但没系统整理过思路的朋友。
1. 上手前的思想准备与命令全貌
1.1 为什么find是排查问题的第一选择
Windows和桌面Linux里,找文件靠搜索框,电脑自己建索引,按一下回车就有结果。服务器环境完全是另一回事:没有图形界面,没有桌面索引,你面对的是一大堆不知道什么时候生成的日志、备份、临时文件。这时候要找一个文件,靠记忆翻目录结构根本不现实。find的特殊之处在于,它不是用索引,而是按你给的条件“问”整个目录树。它会一层一层往下走,每到一个目录就看看有没有符合条件的文件,然后把你想要的路径打印出来。这个过程慢一点,但胜在条件可以很自由——按名字、按大小、按修改时间、按文件类型,甚至组合起来用。
1.2 一分钟看懂find的五段式结构
find命令看着参数很多,其实骨架非常固定。把它的结构拆开,就是:
find 起点目录 查找条件 处理动作我遇到很多新手被find的长命令吓到,其实就是因为没有抓住这个骨架。比如:
find /var/log -name "*.log" -type f从左往右读:在/var/log目录里,找名字符合*.log而且类型是普通文件的项。起点是“去哪里找”,条件是“找什么样的”,而最后面的-type f,本质上是另一个条件。真正符号意义上的“处理动作”,是-print、-delete、-exec这一类。如果不写处理动作,find默认执行-print,也就是把符合条件的完整路径打印到屏幕上。理解这个结构之后,你会发现find其实就是一个“先筛东西、再做事”的工具,前面的参数都是筛子,后面的动作才是真正干活的环节。
2. 实战从名字和类型入手:-name、-iname、-type
2.1 用-name按名字找文件时最容易踩的坑
-name是大多数人接触的第一个find参数,它的作用是匹配文件名。注意这里的“文件名”,只包含文件自身的名字,不包含它所在的路径。比如:
find /home -name "report.pdf"这个命令找的是所有路径下名字刚好是report.pdf的文件,不管它在/home/user/documents还是/home/user/下载。这是理解-name的关键:只要你关心的是“文件叫什么”,而不是“文件在哪”,它就能帮你扫完整棵目录树。
这里的坑在于大小写和通配符。Linux的文件名是区分大小写的,Report.pdf和report.pdf是两个文件。如果你不确定对方用的是大写还是小写,一个方案是用-iname,它会在匹配时忽略大小写:
find /home -iname "report.pdf"另一个坑是通配符要加引号。很多新手直接写:
find /home -name *.log如果当前目录恰好有一个a.log文件,shell会先把*.log展开成a.log,然后find拿到的条件就变成了“名字等于a.log”,这和你想要的完全不一样。正确写法是对通配符加引号:
find /home -name "*.log"2.2 用-type把目录、普通文件、链接分开筛
服务器上一个目录里可能有普通文件、子目录、软链接、管道文件。很多时候你只需要其中某一类。-type就是干这个的。常用场景里,f代表普通文件,d代表目录,l代表软链接。我自己用得最多的是-type f和-type d的组合。比如:
find /opt -type d -name "cache"一个人说“帮我查一下/opt下有没有cache目录”,如果你不加-type d,大概率会找出来一堆cache.log、cache.bak之类的文件。加上类型之后,结果干净很多。再比如清理软链接,某些程序升级后会有残留的current链接指向旧版本,这时候可以:
find /usr/local -type l -name "current"用-type l把软链接筛出来,再逐一检查目标和有效性。这一条在现实排查里非常好用。之所以要先讲-name和-type,是因为“这个项叫什么、它是什么类型”是观察一个文件最基础的两个维度,先把这两个维度练熟,后面的条件组合才不会乱。
3. 文件大小与修改时间的过滤:-size、-mtime、-amin等
3.1 用-size快速揪出吃磁盘空间的元凶
服务器磁盘告警是非常典型的运维场景。一旦根分区使用率超过百分之八十,监控平台就会开始弹消息。这时候最需要的不是看哪个目录大,而是直接找出具体哪些文件最大。-size就是干这个的:
find / -xdev -type f -size +500M这个命令的全盘扫描逻辑需要解释一下。/代表从根目录开始,-xdev的意思是不要跨到其他文件系统或挂载点上去,因为像/proc、/sys、/dev这些虚拟目录里的文件大小是零,而且扫描它们没有意义。-type f保证只找文件,不找目录,-size +500M表示大于500M。组合起来就是:在当前文件系统里找所有超过500M的普通文件。
-size的数值后缀有很多,k代表KB,M代表MB,G代表GB。注意一个小细节:Linux的+500M是大于500M,而-500M是小于500M,没有任何符号就是“正好500M附近”。这个“附近”和通常想的不太一样,因为find按数据块存储判断,会有四舍五入的误差。实操中没人会精确找“正好500M”的文件,都是找大于或小于某个量级的,所以不用纠结这个细节。
3.2 用-mtime定位时间敏感的临时文件
文件大小解决“什么大”,时间参数解决“什么时候”。-mtime是按天算修改时间的,-mmin是按分钟算。它们的语义一致:-mtime +7代表修改时间在7天以前的文件,-mtime -7代表7天以内的,-mtime 7代表刚好第7天的。我在实际工作中最常用的三个组合是备份清理和日志归档:
find /var/log -type f -name "*.log" -mtime +30 -delete这个命令的意思是:/var/log下,名字是*.log、类型是普通文件、修改时间超过30天的,直接删除。这条命令非常实用,但也很危险,容易误删。第一次跑这种命令时,一定要先不加-delete,只把匹配结果列出来一条条看。确认没有漏网之鱼,再真删。
时间参数里还有一个容易迷惑的点:-mtime是文件的修改时间,也就是内容最后被写入的时间;而-atime是访问时间,只要有人读了一次就会变;-ctime是状态改变时间,包括权限变化、重命名这些。日志清理、备份清理优先用-mtime,不要用-atime。因为很多程序会在后台定期读文件,访问时间会被这些行为污染,用-atime判断“这个文件多久没被动过”并不可靠。
4. 把搜索结果直接交出去处理:-exec的三种姿势
4.1 为什么需要-exec
前面讲了多少都只是“把符合条件的文件列出来”。但真实工作里,你可能不只是想看,还想删掉旧日志、改掉错误权限、把文件复制到另一个目录。一个一个去处理显然不现实,这时候就需要-exec出场。-exec的作用是把find找出来的每一个文件,交给后面的命令去处理。
基本语法长这样:
find /tmp -type f -name "*.tmp" -exec rm {} \;拆开看:rm是处理文件用的命令,{}代表“find找出来的每一个文件路径”,\;代表命令的结束。新手最常见的报错就是把\;写成;,因为shell会把分号当成命令分隔符,必须用反斜杠转义一下。{}和\;中间是有空格的,千万不要写成{};,否则rm会收到一个拼接错误的路径。
4.2 从安全删除到批量压缩的实战组合
用-exec能做的事比你想象的多。我自己最常用的几个场景:
删除过期备份。备份目录里每天一个文件:
find /backup -type f -name "*.tar.gz" -mtime +15 -exec rm -f {} \;批量改权限。某人把一批脚本传上来,执行权限丢了:
find /data/scripts -type f -name "*.sh" -exec chmod +x {} \;批量打包归档。找出半年以前的所有日志,全部压到一个归档文件:
find /var/log -type f -name "*.log" -mtime +180 -exec tar -czf /backup/old_logs.tar.gz {} +注意这里是{} +,不是{} \;。\;会把每个文件分别调用一次命令,假设有一万个文件就会执行一万次,慢而且浪费。{} +会把所有文件名一次性作为参数传给命令,效率高很多。区别很简单:需要“每个文件单独做一次”的事,比如rm、chmod,用\;;可以“一批文件一起处理”的事,比如tar、grep,用{} +。
4.3 为什么我更推荐管道加xargs
虽然-exec很方便,但实际工作中我更经常用管道配合xargs。原因很简单:find加上-exec之后,命令串很容易变长变乱,不好读;而xargs的写法更灵活,还能兼顾批量处理。比如同样删文件,用xargs写成:
find /backup -type f -name "*.tar.gz" -mtime +15 | xargs rm -f注意xargs的默认行为是按空白字符切割输入,如果文件名里有空格,到rm那里就会断成两截。为了解决这个问题,需要在find上加-print0、在xargs上加-0,让它们用空字符而不是换行来分隔结果:
find /backup -type f -name "*.tar.gz" -mtime +15 -print0 | xargs -0 rm -f这个写法看起来复杂,但它能处理任意诡异的文件名。你以后在简历或者大牛的脚本里看到-print0和-0,要知道它们是配套的,缺一个就可能在文件名带空格的时候出错。
5. 常见问题排查、防坑指南
5.1 为什么find扫描那么慢还能不能加速
很多小白第一次跑find /的时候,发现命令跑了好久都没结束,以为是卡死了。其实不是,它是在把整个文件系统摸一遍。根目录下挂的东西太多了,光/proc里的虚拟文件数就有成千上万个。我建议新手先明确边界:不要在根目录直接扫,而是先缩小到可疑的目录。比如日志就在/var/log,用户数据就在/home,备份就在某个特定目录。目录越小,find跑得越快,结果也越准。
如果一定要全盘扫,记得加-xdev跳过其他文件系统。这个参数能帮你省掉很多时间,尤其是那些挂载了很大的数据盘的机器。另外,/proc、/sys、/dev这些目录,尽量别去扫。真扫起来不仅慢,还会打印一堆你根本不需要的结果,甚至可能因为读取某些虚拟文件产生异常。需要精准跳过的话,可以用-path加-prune实现,对新手来说不是必须掌握的,知道有这回事就行。
5.2 五个容易踩的坑与排查思路
用find踩坑太正常了,我列几个自己踩过和帮人踩过的典型问题,做成一个速查表。
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 明明有文件却找不到 | 条件写错或大小写不匹配 | 确认-name是否区分大小写,换成-iname试试 |
| 命令报错“No such file or directory” | 起点目录写错了 | 检查find后面第一个参数是不是存在的目录 |
| 删除完发现还有残留 | -name没加引号,shell先展开了通配符 | 所有通配符都加双引号 |
用xargs时报奇怪的错 | 文件名里有空格,被切开了 | 加-print0和-0 |
在脚本里写-exec总报语法错 | 分号没有转义 | 写成\;,别写成; |
5.3 关于-delete和引号包裹的边角问题
-delete用起来解气,但它是真删,不走回收站,没有二次确认。我的习惯是:不管多小的清理任务,第一次都先不带-delete跑一遍,看到匹配列表之后再加。如果条件比较复杂,比如既有-mtime +30又有-name "*.log",我会先用-exec ls -l {} \;列出来看文件变化时间和大小,确认之后再删。这么做会多花十几秒,但能避免把不该删的东西删光。
还有一个容易忽略的点:条件里的引号问题。不只是-name的通配符,凡是用到shell能展开的东西,都要考虑是否加引号。比如-size +100M里的加号,shell不会管它,但为了统一习惯,我把所有带特殊字的参数都用引号保护起来。这个方法在写剧本脚本时尤其重要,因为你不可能预判每个用户的环境会把什么字符当特殊字符。
6. 写在最后的一些真实经验
最后分享一个我自己的习惯。我从来不会在没有任何说明的服务器上直接跑find / -type f -delete这种命令,哪怕只是练手。这就像在一堆文件中间拿了一把刀,手一抖就可能伤到自己。find这个命令本身没有错,错的是不加思考的执行。
我的经验是先把每个参数单独弄熟,再组合。用find的时间不要晚于其他任何Linux命令,但学习它时,也不要一上来就想着“找出来然后全部删掉”。先会找,会看,会筛选,然后再考虑其他处理。等你在实际工作里用find成功定位过几次问题,你会觉得服务器突然变成了一个透明的工具,不再是一堆需要瞎翻的目录。下次再有人喊磁盘满了,你大概可以头也不抬地敲一行find,然后直接告诉他问题在哪儿。