做Linux运维将近十年,我面试过的人里,十个有八个能把ls、cd、cp这些基础命令背得滚瓜烂熟,但真到了生产环境一操作,问题就全冒出来了:软链接指向乱掉、rm -rf后面跟了个空变量、日志把磁盘塞到100%却不知道先清哪个目录。Linux系统的文件与目录管理,表面看是入门第一课,实际上是一根贯穿开发、测试、运维所有环节的主线,它决定了你排查故障的速度、写脚本的质量,甚至是线上事故的多寡。这篇文章我就把自己这些年操作文件系统、管理目录、处理磁盘告警时积累的经验完整地讲一遍,适合刚入门的新手,也适合那些背了一堆命令但始终没有建立起文件系统整体认知的同学。
1. 先搞懂"一切皆文件":Linux文件管理的地基
很多新手学文件管理,一上来就死背命令,结果背了一堆命令还是不会用。原因很简单:命令只是操作文件的手段,而理解文件系统是怎么组织数据的,才是判断"操作会带来什么后果"的前提。
我自己带新人时的习惯是,不管他会不会ls,先让他亲手在/tmp下创建一个文件,用stat命令看这个文件的信息。stat输出的那一堆字段里,有三组时间戳(Access、Modify、Change),还有Inode、Blocks、Device这些词。看着这些字段,其实就能慢慢理解一个文件的真实构成。
1.1 文件不只是一堆数据:inode与目录项的拆解
一个文件在Linux里由两部分组成。一部分叫inode,存的是元数据——权限、属主、大小、时间戳、数据块的位置;另一部分叫目录项(dentry),它记录的是文件名和inode号之间的对应关系。
用最直白的话说:文件名只是一个指向inode的"门牌",数据本身存放在数据块里。你删除一个文件,删除的其实只是这个门牌,对应的inode和数据块在引用计数归零后才会被真正释放。
为什么这对文件管理很重要?因为它直接解释了一系列"诡异"现象:
- 为什么在同一个文件系统内移动文件几乎瞬间完成?因为
mv只是改了目录项里的门牌路径,并没有搬数据。 - 为什么把一个很大的文件从
/tmp移到/data盘(跨文件系统)会很慢?因为这个操作在底层等价于先复制数据、再删除源文件。 - 为什么删除大文件的瞬间,磁盘空间没有释放?因为还有进程打开着这个文件,inode的数据块没有被释放。等你找到那个进程再kill,空间才会回来。
这些都是日常运维里真实遇到的问题。有一次客户报障说磁盘满了,df -h显示/dev/vda1已经100%,但du -sh /看了几遍只有40G左右。最后用lsof +L1一查,是一个已经删掉的日志文件被syslog进程一直占用着。这种场景的处理方案就不是删文件,而是重启或重载对应进程。
1.2 为什么目录、设备、管道在Linux里都是"文件"
Linux设计哲学的核心就是一句话:一切皆文件。这不是为了某种情怀,而是为了统一操作接口。
- 普通文件:存数据。
- 目录:也是文件,但内容比较特殊,存的是文件名与inode的映射表。
- 设备文件(
/dev/sda):通过open、read、write这一组文件接口来操作硬件。 - 管道文件(pipe):进程间通信的通道。
- 套接字文件(socket):网络通信用的。
- 符号链接(symbolic link):记录目标路径的文件。
ls -l输出每行的第一个字符就是文件类型:-表示普通文件,d表示目录,l表示软链接,c表示字符设备,b表示块设备,p表示管道,s表示socket。
这个设计给使用者的实际好处在于:一套通用的文件操作API可以套用到几乎所有对象上,所以不同场景都能用"文件视角"去理解。比如你查看/proc/2950/目录,本质上是在读内核为进程暴露的一组虚拟文件;执行df看到挂载信息、执行free看到内存信息,底层也都是文件接口的呈现。
1.3 每个排查问题的人都需要"回到文件"的角度
我常说一句话:排查Linux问题的第一步,永远别去猜,去看文件、看目录、看状态。程序起不来,先看日志文件,日志写在哪儿、有没有权限写;配置不生效,先把配置文件里对应的项找出来,确认程序加载的是不是这个文件;磁盘紧张,从挂载点往下逐层看目录里的占用;系统启动有问题,看/var/log下的启动日志。
这套思维方式,本质上就是"文件与目录管理"在真实场景里的落地。文件系统逻辑是内功,命令是招式,两者缺一不可。命令忘了可以查man,但理解这套逻辑,才是举一反三的根。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. FHS目录规范:系统目录树不是随便长的
很多人对Linux的目录树无感,觉得就是装系统时自动生成的。直到有一天你发现有些软件装在/opt、有些在/usr/local、有些在/usr/bin,升级系统时被搞混了,才开始后悔没有早点理解它们的区别。
Linux发行版虽然五花八门,但目录结构都遵循一个共同的约定——FHS(Filesystem Hierarchy Standard,文件系统层级标准)。它规定了系统根目录下每个目录的职责范围。你不需要背这个标准,但你需要知道关键的几个目录是干嘛的。
2.1 /bin、/usr/bin、/sbin的职责划分与软链接合并
早期Linux把"系统启动时必须用到的命令"放在/bin,把"系统管理专用的命令"放在/sbin,把"普通用户安装的第三方程序"放到/usr/bin。后来发现这样分下去目录越来越多、越来越乱,所以现代主流发行版(CentOS 7之后、Debian系、Ubuntu)做了一个关键动作:把/bin、/sbin、/lib全部做成指向/usr下对应目录的软链接。
所以你在CentOS上会看到/bin -> usr/bin。很多初学的同学第一次看到会愣住:怎么/bin是个链接?这不影响使用,但理解之后,升级迁移时就不会重复去修改两个目录了。
那什么时候用/usr/local?我的经验是:通过源码编译、手工安装的软件,优先放到/usr/local,默认的prefix就是/usr/local。这样有一个好处:系统自带的软件包(由发行版包管理工具管理)放在/usr,你自己编译的程序放在/usr/local,两者分开,卸载升级互不干扰,排查冲突也方便。
而/opt一般是给第三方商业软件的。很多大厂软件(比如Oracle、部分数据库、网管工具)默认安装到/opt,原因就是FHS给/opt的定义是"存放可选的第三方应用软件包",它独立于系统本身。
2.2 /etc、/var、/tmp的用途与边界
这三类目录在生产里最容易被忽略,我整理过一张自己的速查表:
| 目录 | 核心用途 | 典型内容 | 需要警惕的点 |
|---|---|---|---|
/etc |
系统配置文件 | passwd、shadow、resolv.conf、hostname | 几乎必备份,出问题先查这里 |
/var |
可变数据 | /var/log、/var/cache、/var/spool |
日志无限增长,磁盘报警高发区 |
/tmp |
临时文件 | 各种运行时临时文件 | 重启可能被清空,重要数据绝不能放 |
/run |
运行时数据 | pid、socket文件 | 系统启动时生成,别手工乱改 |
/dev/shm |
共享内存 tmpfs | 共享内存文件 | 被占满说明有进程在写共享内存 |
关于/tmp有一个细节:很多系统把它挂载成tmpfs(基于内存的文件系统),速度很快,但重启即失。在/tmp里跑大数据聚合任务、放临时编译产物是可以的,存重要数据就是给自己挖坑。
2.3 管好目录之前先学会看磁盘:df与du的配合
文件与目录管理绕不开一个场景:检查磁盘空间。df和du是我运维里用得最频繁的两个命令,但很多人对它们的区别并不清楚。
df:从文件系统层面看整体使用情况,常用df -h。du:从目录树层面统计某个目录的累计占用,常用du -sh /path、du -h --max-depth=1。
两者的关键区别是:df看到的是块设备的整体占用;du是遍历目录下文件大小累加出来的。所以当df显示满了、但du统计出来不大时,可能存在三种情况:有被删除但仍被占用的文件、有挂载点掩盖了子目录的真实占用、或者有隐藏文件/特殊文件没被统计进去。
我的排查思路固定如下:
df -h先确定哪个挂载点满了。du -h --max-depth=1 /按层级找占用大的目录,逐层往下。- 如果
du和df对不上,用lsof +L1找已删除但仍被占用的文件。 - 再用
find检查有没有超大文件。
这套组合拳在磁盘告警场景里基本够用,后面实战章节我会用完整案例演示。
3. 高频命令实操:从"会用"到"用得对"
很多同学问"Linux常用命令大全背不完怎么办",我的建议是:不用背完,先把文件与目录管理最核心的几个命令吃透,搞清楚每个命令能干什么、有哪些隐藏参数、什么时候会有坑,比把一百个命令都背下来要有用得多。下面的命令看起来都基础,但关键点全在细节里。
3.1 ls、cd、pwd:每个人都会,但没几个人用全
这三个命令太基础了,以至于很多人工作几年还在用最初级的用法。几个容易被忽略但非常好用的组合:
ls -lt按修改时间倒序,最上面就是最新修改的文件,排查哪个日志在疯长时一打一个准。ls -S按文件大小倒序,找大文件很快。ls -d */只列当前目录下的一级目录,不展开内容。ls -la是查详细列表的标配,但不能只知道这个。
cd -是我在命令行里用得最多的快捷键之一,直接回到上一次所在的目录。当你从/opt/app/config切到/var/log查完日志,想回到原来的配置目录,一个cd -搞定,不用重新敲一长串路径。
pushd和popd是目录栈管理,适合需要频繁在多个深路径之间切换的场景。交互式操作里用起来有奇效。
这里有一个大部分人不知道的坑:在软链接目录内,直接pwd显示的是逻辑路径(你进入时用的路径),加pwd -P才显示物理真实路径。我之前排查过一个脚本异常,原因就是脚本里读取pwd,但目录是软链接进去的,取到的路径不是真实路径,导致后续相对路径全部错位。
3.2 cp、mv、rm的关键参数与隐藏行为
cp看起来简单,但生产环境里踩坑无数。核心参数:
cp -a:归档模式,等于-dR --preserve=all,复制目录时保留权限、属主、时间戳、软链接本身。备份配置文件时我几乎必用cp -a。cp -p:只保留基本属性(权限、时间戳、属主),比-a轻量。cp -r:递归复制,但注意它不保证保留链接属性。网上很多人建议"复制目录一定要用cp -a而不是cp -r",原因就在这:cp -r可能会把软链接指向的内容复制成实体文件,导致所有软链接全部变成一份份实体文件。我遇到过同事把带软链接的配置目录用cp -r复制去新机器,启动时路径全乱。cp -u:只在源文件比目标新、或目标不存在时才复制,做简单增量同步时很实用。
mv的关键点:同文件系统内mv是改目录项,瞬时完成;跨文件系统mv等于copy+delete,速度取决于文件大小。大文件跨盘移动不要用mv去搬,要规划好时间或用rsync中转。
rm的坑最大,主要是rm -rf。我在生产服务器上的习惯是:
- 非交互脚本里,先
echo出要删除的路径,确认无误再执行删除。 - 能用
find -delete的尽量用find -delete,它按条件精准匹配,不容易因为变量为空或通配符膨胀出事故。 - 交互式操作建议
alias rm='rm -i',但脚本里alias默认不生效,所以脚本里要靠代码检查。
这里顺便回一个面试高频点:rm删除文件后,磁盘空间就释放了吗?答案是不一定。只要有进程仍持有这个文件句柄,磁盘空间就不会释放,直到进程关闭文件或进程结束。
3.3 find、grep、xargs:检索与批处理的铁三角
find是我认为文件与目录管理里最值得花时间啃的命令,没有之一。它的基本逻辑是:确定起点路径、用表达式匹配条件、对匹配结果执行动作。三种最常用的筛选条件:
bash复制# 按文件名
find /data -name "*.log" -type f
# 按时间(30天前修改过)
find /data -type f -mtime +30
# 按大小(超过100M)
find /data -type f -size +100M
组合条件时,注意括号的优先级。比如要找".log"结尾且修改于7天前的:
bash复制find /var/log -type f -name "*.log" -mtime +7
要找".log"或".txt"结尾的,必须加括号:
bash复制find /var/log -type f \( -name "*.log" -o -name "*.txt" \)
如果不加括号,-o会破坏默认的and逻辑,结果完全不符合预期。这是新手很容易踩的坑。
find的两个高风险动作是-exec和-delete。合理用法是这样:
bash复制# 直接删除30天前的日志
find /data/logs -type f -name "*.log" -mtime +7 -delete
# 先压缩再处理
find /data/logs -type f -name "*.log" -mtime +7 -exec gzip {} \;
-exec后面的{}代表匹配到的文件名,最后的\;是exec语句结束标记,写错一个反斜杠或分号,整个命令就会报错或产生意想不到的结果。
xargs的作用是接管find的输出,把结果作为参数传给另一个命令。它有个著名的坑:文件名带空格时,默认按换行分隔会出错,标准安全姿势是:
bash复制find /data/logs -type f -name "*.log" -print0 | xargs -0 rm -f
-print0让find用空字符而不是换行作为分隔符,xargs -0也按空字符解析。带空格、换行的文件名都能正确处理。
grep在三板斧里主要做内容匹配。grep -r在目录里递归搜、grep -l只列文件名、grep -v反选。配合find可以做"先找文件再搜内容"的联合操作,比如排查Nginx配置里哪些文件用了某个server_name:
bash复制grep -rl "example.com" /etc/nginx/
3.4 tar:批量备份与搬迁的顺手工具
tar的价值不只是压缩,更是一种"打包批量操作"的思路。一条命令就把整个应用目录带走:
bash复制tar czf /backup/app_20251212.tar.gz /opt/app
配合scp或rsync就能搬到别的机器。几个容易忽略的参数:
-C:解包到指定目录。--exclude:排除目录,比如不打包日志。-t:只查看包内容,不解包。--strip-components:解包时去掉前N层目录结构。
bash复制tar xzf app.tar.gz -C /tmp/app
tar czf app.tar.gz /opt/app --exclude=/opt/app/logs
tar tzf app.tar.gz
tar解压有个坑:默认会把包内的路径解到当前目录。如果包内路径是./opt/app/xxx,在错误目录解压会散落一地。所以解压之前用tar tzf先看包内结构,养成习惯就不会乱。
4. 权限体系拆解:Linux安全边界的内核
文件与目录管理如果只会增删改查,那只是基础操作。真正让Linux在多人、多服务场景下稳定运行的关键是权限体系。权限管理不到位,文件管理做得再好也会出大问题。
4.1 权限位到底怎么读:文件和目录的rwx语义不同
ls -l输出的第一列,比如-rw-r--r--,拆开看结构是:
- 第1个字符:文件类型。
- 第2-4位:属主权限(user)。
- 第5-7位:属组权限(group)。
- 第8-10位:其他人权限(other)。
rwx在普通文件上的含义很清晰:r能读内容、w能修改内容、x能执行。但权限加到目录上,很多新人就迷糊了,这里单独强调:
- 目录的
r:能列出目录中的文件名。 - 目录的
w:能在目录里创建、删除、重命名文件。 - 目录的
x:能进入目录,或者说"穿越"目录。
关键点在于:如果你有r但没有x,你能ls看到文件名,但cd不进去,也访问不到文件详情。所以给目录授权时,三个权限最好成套考虑。
还有一个非常反直觉但面试高频的点:对文件有w权限,并不意味着你能删除它。能否删除文件,看的是文件所在目录的w权限。换句话说,只要目录允许,一个对文件没有写权限的用户,也可以删掉这个文件(除非受到后面要讲的sticky bit限制)。这个特性在多用户共享目录时尤其要当心。
4.2 数字权限与umask的换算逻辑
数字权限的本质是二进制位的加权。r = 4、w = 2、x = 1,把三个位置的数值加起来就是这一档的权限。rwx就是7、rw-是6、r-x是5、r--是4。
我见过不少同学死记644、755,但遇到非标准需求就晕了。建议直接理解换算逻辑:644 = 属主rw(6)、属组r(4)、其他人r(4),常见于普通文件;755 = 属主rwx(7)、属组r-x(5)、其他人r-x(5),常见于目录和可执行脚本。要组合出属主可读写执行、组只读、其他不可访问,那就是740。
umask决定新建文件/目录的默认权限。默认文件最大是666,目录是777,然后减去umask值对应掉的权限位。例如umask是022时,新建文件是644、目录是755;umask是002时,新建文件是664、目录是775。
umask设置要考虑实际场景。服务器上如果多个团队共享一个部署目录,umask设成022可能导致组内其他同事无法修改你生成的文件,结果就是各种permission denied。我见过最典型的场景是:一台Web服务器上,运维用root部署程序,umask是022,结果程序员用www用户想改一个配置文件,一点权限都没有。这种问题根源不在文件权限本身,而在部署账号和umask策略没配合好。
4.3 特殊权限位:setuid、setgid、sticky bit
普通权限之外,还有三个特殊权限位。用数字表示时是四位数,加在传统三位前面:
- 4xxx:setuid。主要用于可执行文件,程序运行时就以文件属主的身份运行,而不是执行者的身份。典型是
passwd命令:普通用户通过它修改密码时,需要以root身份去写/etc/shadow,所以/usr/bin/passwd带有setuid位。 - 2xxx:setgid。用于目录时,新创建的文件会自动继承该目录的属组;用于文件时,进程以文件属组身份运行。
- 1xxx:sticky bit。只对目录有意义。设置了sticky位的目录,只有文件属主、目录属主或root用户能删除/重命名目录里的文件。典型代表是
/tmp,任何人都可写,但普通用户不能删除别人创建的临时文件。
查看方式:ls -l里权限位会出现s或t小写字母,比如-rwsr-xr-x表示setuid,drwxrwxrwt表示sticky。如果看到的是大写S或T,说明对应的x位没设,那这个权限配置基本是错的。
设置命令:
bash复制chmod 4755 /path/to/file
chmod 1777 /tmp
chmod 2770 /shared/dir
也可以符号模式:chmod u+s file、chmod g+s dir、chmod +t dir。
生产环境中setuid位要格外谨慎。一个可执行文件如果属主是root又带setuid,一旦程序本身有漏洞,就可能被利用来提升权限。常规安全基线都会要求扫描root所有且带setuid位的文件。我看到群里有人分享"给某条命令加setuid解决权限问题"时,第一反应永远是劝他再想想,除非你能完全确认风险可控。
4.4 当权度过细:ACL与sudo授权边界
传统权限方案只有user、group、other三档。多团队共用一个目录时,需求通常是"目录里用户A可读写、组B可读、其他人不可访问"——传统权限做不到,这时候用ACL(访问控制列表)。
ACL的命令是getfacl和setfacl。常用写法:
bash复制# 给用户devuser设置对/data/app的rwx权限
setfacl -m u:devuser:rwx /data/app
# 给组devgroup设置r-x
setfacl -m g:devgroup:r-x /data/app
# 查看权限
getfacl /data/app
# 移除用户权限
setfacl -x u:devuser /data/app
# 清除所有ACL
setfacl -b /data/app
文件被设置了ACL后,ls -l的权限位末尾会多一个+号。此时你再去chmod,只能影响传统的属主/组/其他对应的位,ACL条目需要单独用setfacl调整。有些同学chmod之后发现"权限怎么没生效",大概率就是文件带着ACL,而你修改的只是传统位,ACL里更细粒度的条目还在起作用。
提权管理这块,sudo也是文件管理绕不开的一部分。sudo的核心是/etc/sudoers里授权哪些用户能以什么身份执行哪些命令。比如让普通用户dev能重启Web服务:
text复制dev ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx
这里的边界原则就是"只给最小需要的命令权限",不要图省事给ALL。生产上很多事故都是因为sudo权限给得太宽,一句误操作把整个目录都删了。文件与目录管理的安全,不只靠权限位,还要靠操作者的授权边界控制。
5. 硬链接与软链接:两个"快捷方式"的底层差异
链接是文件与目录管理里非常容易混淆的知识点。我每次讲到这里都会让学员先想一个问题:Windows桌面上的快捷方式,到底是Linux里的软链接还是硬链接?答案:它是软链接的近似物,因为快捷方式本身是一个独立文件,保存的是指向目标路径的信息,而不是数据的副本。
但Linux还有一个硬链接概念,它和快捷方式完全是两回事。"文件是inode+目录项"这个模型,是理解两种链接最好的入口。
5.1 硬链接:同一个inode的多个姓名
硬链接的原理就是让多个目录项指向同一个inode。也就是说,同一份文件数据,有两个或更多不同的路径名可以访问。你修改任何一个名字下的内容,另一个名字下看到的内容也跟着变了,因为数据是同一份。
bash复制# 创建硬链接
ln /data/a.txt /data/b.txt
# 查看inode号,a.txt和b.txt的inode完全相同
ls -i /data/a.txt /data/b.txt
ls -l里硬链接数那一列会从1变成2。
硬链接有硬性限制:
- 不能跨文件系统。不同文件系统各自维护自己的inode编号,体系上不允许跨文件系统创建硬链接。
- 不能对目录做硬链接(除系统内部保留的
.和..外)。原因很简单:目录树如果允许随意硬链接,就可能出现循环引用,导致文件系统遍历算法无限循环。 - 删除一个硬链接,不会立即删除数据,只是把inode的链接计数减1。只有当计数归零,文件数据才真正释放。这个机制也解释了一个现象:为什么
rm删除了日志文件,进程还在写它时,磁盘空间依然被占用。
硬链接在运维里的典型用途是给程序的可执行文件做别名,或者做多路径备份。不过说句实话,现在生产环境里硬链接用得不算多,反而容易踩坑——很多刚接触的人无意中给日志文件建了硬链接,然后清空一个、忘记处理另一个,结果日志还在增长;或者删了主文件以为磁盘空间释放了,实际上另一个硬链接还占着数据。
5.2 软链接:记录路径的"便签"
软链接(符号链接)是一个独立的文件,它有自己的inode,内容就是目标路径的字符串。
bash复制ln -s /data/app /data/current
软链接的机制决定了它的特性:
- 可以跨文件系统,因为存的是路径,不是inode引用。
- 可以指向目录,也允许指向不存在的目标。这时链接还在,但目标失效,访问会报
No such file or directory。 - 目标路径既可以是相对路径也可是绝对路径。注意:相对路径是相对于链接文件所在目录,而不是相对于你执行命令的目录,这个经常把新手坑到。
软链接最常见的坑是链式断裂。比如:
bash复制ln -s /data/app_v1 /data/current
# 后来 /data/app_v1 被重命名或删除
cd /data/current/xxx # 报错
正确做法是重新指向:
bash复制ln -sfn /data/app_v2 /data/current
注意-n参数,它告诉ln在替换目录链接时,不要生成奇怪的嵌套行为。
还有一点:软链接的权限位看起来是777,但这是误导。实际访问权限取决于目标文件,而不是链接文件本身。很多人给软链接chmod 777发现没用,就是因为权限判断的是目标文件。
5.3 实战:用链接解决多版本软件切换问题
一个我常用的场景是Java程序多版本共存的目录管理。假设你有jdk-8和jdk-17两套版本:
text复制/data/java/jdk8
/data/java/jdk17
建立一个软链接/data/java/current,先指向jdk8。启动脚本里写JAVA_HOME=/data/java/current。要升级到jdk17时,只需:
bash复制ln -sfn /data/java/jdk17 /data/java/current
程序配置一行都不用改,下次重启直接用的是新版本。如果新版本有问题,再把链接改回jdk8,秒级回滚。这个"通过软链接做版本切换"的模式,在Nginx、Node.js、Python虚拟环境、应用发布等很多场景都适用。
核心思想就是一句话:配置里永远不写具体版本路径,只写软链接路径;切换版本就是改一个链接,而不是改一堆脚本。这在发布系统里是一种非常干净的解耦方式。
对比一下两种链接:
| 对比项 | 硬链接 | 软链接 |
|---|---|---|
| inode | 与原文件相同 | 独立inode |
| 本质 | 多个目录项指向同一inode | 一个存路径的独立文件 |
| 跨文件系统 | 不行 | 可以 |
| 指向目录 | 不行 | 可以 |
| 目标被删除 | 原文件数据仍在,链接仍有效 | 链接失效,成为断链 |
| 访问权限 | 与目标文件一致 | 取决于目标文件的权限 |
| 创建命令 | ln 源文件 链接名 |
ln -s 目标 链接名 |
6. 一个完整实战:日志目录的清理与归档
前面讲了原理和命令,最后落到一个真实场景里串一遍。日志管理是"文件与目录管理"在生产中最常见的应用场景,没有之一。我就用一次完整的磁盘告警处理来演示整体思路。
6.1 从df报警到定位元凶的排查链路
假设你收到监控告警:/dev/vda1使用率超过90%。处理流程:
bash复制# 1. 确认哪个挂载点快满
df -h
# 2. 查看根目录下各子目录占用
du -h --max-depth=1 / 2>/dev/null | sort -hr | head -20
# 3. 顺着最大的目录继续下探
du -h --max-depth=1 /var/log | sort -hr | head -20
# 4. 锁定具体文件,按时间倒序看最新的日志
ls -lht /var/log/nginx/
这里有个细节:du用--max-depth=1,能直接列出每个子目录的占用,配合sort -hr按人类可读大小倒序排,一眼就能看到大的目录在哪。如果用--max-depth=0,只显示当前目录总量,对定位没有帮助。
6.2 用find实现按时间与大小的精准清理
定位到/var/log/nginx下有一批访问日志,很多是30天前的,还有几个超大文件。此时不要手动一个个删,而是用find精准筛选:
bash复制# 删除30天前的.log普通文件
find /var/log/nginx -type f -name "*.log" -mtime +30 -delete
想更稳妥一点,先数一下有多少、总共多大再动手:
bash复制# 查看匹配到的文件名
find /var/log/nginx -type f -name "*.log" -mtime +30 -exec ls -lh {} \;
# 统计匹配文件的总大小
find /var/log/nginx -type f -name "*.log" -mtime +30 -exec du -ch {} + | tail -1
注意第二条命令里我用到的是du -ch {} +,末尾是加号+不是分号;。加号表示把匹配到的所有文件一次性传给du,这样能得出总和。很多同学这里漏了+,结果每一行单独统计,看不到总数。
如果某个日志文件特别大(比如单文件几十G),且不需要保留,不要直接rm,更推荐用清空内容的方式:
bash复制> /var/log/nginx/access.log
# 或者
truncate -s 0 /var/log/nginx/access.log
这样日志文件大小归零,但Nginx进程还握着文件句柄,继续写入不会有问题。如果直接rm,Nginx还会往已删除文件的inode上写,磁盘空间不会释放,必须重启Nginx才能释放,这往往是没必要的风险操作。
6.3 归档与压缩:给历史日志一个体面的去处
不是所有日志都该删。有些日志要保留30天、90天甚至一年。这时候就要做归档压缩。我的做法是先用find把7天前的日志打包归档到/backup/logs:
bash复制find /var/log/nginx -type f -name "*.log" -mtime +7 -print0 | xargs -0 tar czf /backup/logs/nginx_$(date +%F).tar.gz
然后再删除原目录中超过30天的:
bash复制find /var/log/nginx -type f -name "*.log" -mtime +30 -delete
这里用到-print0和xargs -0,目的就是防止文件名里有空格时出错。日志文件名一般很规整,但这个习惯值得养成。
生产上一套完整的日志管理通常还会结合logrotate做轮转,它的亮点是按大小或时间自动触发切分、压缩、清理,并且能通知进程重新打开日志文件。logrotate的配置在/etc/logrotate.d/下,比如Nginx的配置长这样:
text复制/var/log/nginx/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 640 nginx adm
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
endscript
}
含义是:每天轮转一次,保留7份,压缩旧日志,轮转后向Nginx发USR1信号让它重新打开日志文件。手动清理是应急,logrotate是日常规范,两手都要有。
7. 那些文件与目录管理中最容易翻车的瞬间
写到最后,我把这些年亲眼见过的"翻车现场"集中归纳一下,希望看到的人少踩几个坑。虽然都是具体场景,但背后的逻辑完全可以用前几章讲到的文件系统知识来解释。
7.1 rm -rf翻车记:变量为空时会发生什么
最经典的翻车操作是shell脚本里的rm -rf后面跟一个变量,变量为空会导致命令变成rm -rf /,把整个系统删掉的事故在国内外都不少见。
我见过的翻车版本大概长这样:
bash复制DIR=/data/app
rm -rf $DIR/logs
如果DIR变量为空或被手动置空,这一句就可能变成rm -rf /logs,甚至更严重。解决方案有几个层次:
- 在变量使用前检查是否为空:
bash复制if [ -z "$DIR" ]; then
echo "DIR is empty, exit"
exit 1
fi
- 脚本开头加
set -u,让未定义变量直接报错退出。 - 使用更安全的替代命令,比如
find "$DIR" -type f -mtime +7 -delete,即使路径错了,find至少会因为没有匹配到内容而报错,而不至于直接删根。 - 设置
alias rm='rm -i',不过脚本里alias默认不生效,所以脚本内还是得靠前面几种。
顺便说一句,虽然现代系统一般有--no-preserve-root防护,rm -rf /不一定真能执行,但你永远不要把自己的安全寄托在一条命令的"默认保护"上。更稳的运维策略是:给重要目录做回收站式删除,或者用mv到trash目录再定期清理。
7.2 特殊字符文件名:空格、横线、换行符的攻防
文件名里有空格是常见问题。比如你创建了一个带空格的文件my report.txt,直接rm my report.txt会被shell解析成两个参数,完全不是你想删的文件。正确姿势:
bash复制# 引号包裹
rm "my report.txt"
# 转义空格
rm my\ report.txt
# 用find按条件删除,避免手动拼文件名
find . -name "my report.txt" -delete
以横线开头的文件名更阴险。直接rm -file会让rm以为-file是参数,报错invalid option。正确姿势是加--,明确告诉命令后面都是文件:
bash复制rm -- -file
最狠的是文件名里带换行符。这种文件绝大多数是脚本错误创建的,删除时要靠find -print0配合xargs -0,或者用Python等语言处理。这也是我一直强调批量操作文件时-print0和-0是保命参数的原因。
顺带提一个反直觉点:Linux文件名对大小写敏感,file.txt和File.txt是两个文件。Windows习惯带到Linux上,很容易出现"找不到文件"的困惑。
7.3 磁盘明明满的,du却查不出大文件
最后一个高频问题是:df -h显示磁盘已满,但du -sh统计根目录下所有文件夹,加起来远小于磁盘容量。排查思路:
- 检查被删除但仍被进程占用的文件:
lsof +L1,看到deleted状态的条目,记下进程PID。 - 确认是否有挂载点掩盖了目录数据。把新磁盘挂载到非空目录上时,原目录下的文件会被隐藏。
df看挂载点正常,du看某个目录可能只显示挂载点的大小,原目录的真实占用被掩盖了。用mount检查挂载关系,卸载后才能看到原目录内容。 - 检查隐藏文件和大文件:
bash复制find / -xdev -type f -size +500M -exec ls -lh {} \;
- 检查inode是否耗尽:
df -i。如果inode满了,即使有剩余磁盘空间,系统也会报No space left on device,因为根本无法创建新文件。这种场景通常是小文件太多导致,比如某个程序的缓存目录生成了海量小文件。这种情况要按时间批量清理小文件,而不是单纯删大文件。
这个案例充分说明:文件与目录管理考验的不是你背了多少命令,而是你能否从文件系统机制的角度,把问题一层一层拆开看。df、du、lsof、find、mount不是孤立的一堆命令,它们组合起来就是一套完整的诊断语言。
最后再分享一个压箱底的习惯:我在所有生产服务器的全局profile里做了危险命令的交互保护:
bash复制alias rm='rm -i'
alias mv='mv -i'
alias cp='cp -i'
虽然alias在非交互shell里不生效,但在交互式操作时能挡住一半的手滑。再配合"执行危险命令前先pwd确认当前目录、ls确认目标确实存在"的习惯,这些年用下来,文件管理层面的重大事故基本没再发生过。你可以不认同这个方式,但值得试试。
