先说说这个标题背后的真实场景。绝大多数人搜这句话的时候,服务器磁盘已经快满了,或者某个业务目录增长异常,需要立刻定位到底哪个文件夹在吃空间。我自己处理过的磁盘告警少说也有几十次,从一句话的需求出发,实际上要解决的是“快速定位占用源头并安全清理”这一整套动作。这篇文章就把我在 Linux 服务器上查看文件夹占用空间、定位大目录、排查空间异常的所有常用方法和踩坑记录整理出来,适合刚接触运维的开发者,也适合需要临时上服务器救火的业务同学。
1. 需求拆解:一句话标题背后其实是三件事
1.1 你真正想知道的是什么
“查看某文件夹占用空间大小”这句话实在太笼统了,实践里它至少包含三层需求:
第一层是想知道某个目录总共有多大,比如 /var 已经 50G 了,这通常是看一眼就能回答的问题。
第二层是定位这个大目录里到底哪个子目录或者哪个文件最占空间,这个才是排查磁盘告警的真正核心。如果只是知道 /var 有 50G 却不知道里面是什么,等于没查。
第三层是判断整个磁盘分区还剩多少可用空间,因为有时候你看到的文件加起来并不大,磁盘却满了,这说明空间挂在别的地方,比如其他目录、隐藏文件、或者被删除但仍被进程占用的文件。
很多人一上来就敲du -sh 目录名,这个命令没问题,但它只能回答第一层问题。而真正难处理的是第二层和第三层,这也是这篇文章的重点。
1.2 两条技术路线到底怎么选
Linux 下查看空间占用有两条完全不同的技术路线,必须区分清楚。
du(disk usage)走的是目录树,它从指定路径开始,逐个遍历目录项和文件,累加每个文件实际占用的磁盘块大小。它回答的问题是“这个文件夹里的所有内容加起来占了多少磁盘空间”。
df(disk free)走的是文件系统元数据,直接读取超级块里的统计信息,打印整个文件系统分区的总容量、已用量和剩余量。它回答的问题是“这个分区还有多少空间可用”。
两者看似差不多,实际经常不一致,原因就是统计维度不同。后面第 4 节我会专门展开讲两者不一致的几种情况。先记住一个原则:看单个目录或者整个目录树的大小用du,看磁盘分区整体剩余空间用df,两个必须配合使用。
2. du 命令的核心参数与统计原理
2.1 为什么 du 显示的数值和 ls 的 size 不一样
这是我被问过最多的问题。在某个目录里执行ls -lh,看到一个文件显示 100M,但du -h 文件名却显示 104M 或者 98M,很多人就懵了。
原因在于ls -l显示的是文件的“逻辑大小”,也就是文件内容的实际字节数;而du统计的是文件在磁盘上“物理占用”的块大小。文件系统是按照块(block)来分配空间的,一个块通常是 4K。哪怕一个文件只有 1 个字节,它也要占一个完整的 4K 块,du就会按 4K 来算。反过来,如果是稀疏文件(文件中间有大量空洞),文件逻辑大小可能很大,但实际分配的块很少,du统计出来的就比ls -l小很多。
所以当你在博文或者文档里看到du的结果和网页上某个文件标注的大小不一样,不要觉得是命令用错了,这本身就是 Linux 文件系统分配机制的正常表现。你要做的是理解du统计的是“占磁盘空间”,而不是“文件有多长”。
2.2 必须记住的常用参数组合
du的参数不少,但日常排查其实只需要记住我下面列出的这几个组合,基本覆盖了绝大多数场景。
先给出最基本的一些参数说明:
| 参数 | 作用 | 使用场景 |
|---|---|---|
-h | human-readable,以 K/M/G 为单位显示 | 几乎所有场景 |
-s | summarize,只显示指定目录的总大小 | 精确查看单个目录 |
-a | all,同时显示目录和文件 | 需要看到具体文件时 |
--max-depth=N | 最多递归 N 层子目录 | 定位大目录时最常用 |
-c | 最后显示所有目录的总计 | 统计多个目录的和 |
--exclude=PATTERN | 排除匹配模式的文件/目录 | 跳过日志或缓存目录 |
-x | 不跨越文件系统边界 | 避免统计到挂载的其他磁盘 |
用的时候我建议直接按这样的组合去记:
# 查看单个目录总大小 du -sh /var # 查看当前目录下所有一级子目录的大小,并排序 du -h --max-depth=1 . | sort -hr # 如果想连文件一起显示 du -ah --max-depth=1 . | sort -hr # 统计多个指定目录的总大小 du -sch /var/log /tmp /home注意du -h --max-depth=1和du -sh *执行结果其实差不多,但前者更规范。du -sh *的问题在于它会把 command-line arguments 展开后才去统计,如果当前目录下文件特别多,会有一瞬间的参数列表过长风险,而且用*会漏掉隐藏文件,这一点后面还要单独说。
2.3 用 sort 把结果按大小排序
du默认输出是不排序的,目录一多根本看不出来谁最占空间。所以必须和sort配合。
du -h --max-depth=1 /var 2>/dev/null | sort -hr | head -20sort -hr的意思是按人类可读的数值(4K、100M、2G 这种格式)逆序排序。这里有一个关键细节:如果你直接用sort -h,它会正确识别 K/M/G 单位的大小关系;但如果用sort -r普通排序,会把带 G 的和带 M 的混在一起排错。我第一次用就犯过这个错误,以为 10M 比 2G 大,其实是把字符串当字典序排了。
另外2>/dev/null一定要加上,因为du在遍历过程中遇到没有权限的目录会输出du: cannot read directory之类的报错,这些错误会混进输出里干扰排序。
3. 一套可以直接抄作业的排查流程
3.1 第一步:用 df 判断整体情况
所有磁盘空间排查都应该先从df开始,而不是直接du。因为你需要先搞清楚到底是哪个分区满了,确认优先级。
df -h df -idf -h是查看磁盘容量使用率,df -i是查看 inode 使用率。我多次遇到一种情况:磁盘容量显示还有 20G 空余,但业务报错“No space left on device”,这时候df -i会发现 inode 已经 100% 占满了。inode 是文件系统里记录文件元数据的索引节点,小文件特别多的目录(比如邮件队列、缓存目录、容器镜像层)最容易把 inode 耗尽。判断顺序应该是先df -h看容量,再用df -i查 inode,两个都正常再继续往下走。
3.2 第二步:从根目录逐层下钻定位
整体空间都确认了之后,进入定位大目录的环节。
第一次先看根目录下所有一级目录的大小,因为一般不会一上来就有针对性的目录猜测,先看全局状态:
du -h --max-depth=1 / 2>/dev/null | sort -hr | head -20输出结果里通常会看到/var、/usr、/home里有一个特别大,于是进入对应目录再次执行:
du -h --max-depth=1 /var 2>/dev/null | sort -hr | head -20就这样一层一层往下钻。这个过程我自己的经验是“每次只看一级目录”,不要试图一步到位直接du -h --max-depth=3 /,那样的输出会非常长且杂乱,因为最大深度 3 会把几百个目录全部列出来。你反而会被扑面而来的信息淹没,没法判断重点。逐层下钻虽然多敲几次命令,但每次都只需要关注 top 10 的目录,思路非常清晰。
到某一层出现大量平铺的文件时,再改用du -ah把文件也显示出来,直接定位到具体文件:
du -ah --max-depth=2 /var/log 2>/dev/null | sort -hr | head -203.3 第三步:千万别漏了隐藏文件和挂载点
这一步是新人最容易踩的坑。
du -h --max-depth=1统计的是当前目录下所有条目,但如果你用du -sh *这种方式,shell 的*默认不会匹配以.开头的隐藏文件和目录。比如/home下用户的.cache目录、/var/spool下可能有隐藏目录,这些全都会被漏掉。结果就是排查了半天,明明某文件夹占用很高,但统计结果却对不上。
所以我特别建议你执行下面的互补命令,专门检查隐藏目录的大小:
du -h --max-depth=1 /要查的目录 2>/dev/null | sort -hr du -sh /要查的目录/.[!.]* 2>/dev/null第二条命令里的.[!.]*匹配的是以点开头但第二个字符不是点的条目,也就是隐藏目录和隐藏文件,但不包括.和..。它的作用就是专门揪出被普通通配符漏掉的隐藏文件。遇到过最典型的一次,某台服务器/root下面看起来什么都没有,结果du -sh /root/.*一查,一个.maildump隐藏目录占了 80G,就是某个异常任务一直在往里面写临时文件。
另外还要注意挂载点。du默认会穿越文件系统边界继续统计,比如某个目录下面挂了一个 NFS 或者另一块数据盘,du会把挂载点里的内容也算到当前目录头上,导致这里显示的占用不代表它占用了你当前这个分区。这时候要加-x参数,跳过其他文件系统:
du -x -h --max-depth=1 / 2>/dev/null | sort -hr3.4 找到大文件之后的清理验证
定位到具体的大文件之后,不要冲动地直接rm。先确认这个文件的用途,特别是日志文件,有些进程会持续写入,你手动删掉了但进程还持有文件句柄,磁盘空间并不会释放。
确认要清理之后,在删除前记录一下删前后空间情况:
df -h /var du -sh /var/log rm -f /var/log/某个大日志 df -h /var du -sh /var/log前后对比一下能确认空间是否真的释放了,若删除了但df显示可用空间没有增长,则说明文件被某个进程占用了,具体处理方式我在第 4 节细说。
4. du 和 df 显示不一致的深层原因
4.1 文件被删除但进程仍持有句柄
这是生产环境最经典的问题:你在某个目录里看到一个大文件,执行rm删掉了,目录空间统计也正常了,但df -h一看,整个分区的可用空间没有任何变化,还是满的。
原因在于 Linux 的文件删除机制。rm删除的只是对文件的一个目录项链接,但如果有一个进程正打开着这个文件,文件仍然存在于磁盘上,只是不再有路径可以访问到它。那个进程不关闭这个文件句柄,空间就永远不会释放。
遇到这种情况,用lsof找出持有已删除文件的进程:
lsof +L1+L1会列出“链接数小于 1”的文件,也就是被删除但仍被打开的文件。输出里找到进程 PID 后,要么重启该进程,要么让服务方重新加载配置文件。如果是一台临时服务器急着要空间,确认进程可以重启就直接重启服务。千万不要为了释放空间去kill -9一个你不知道是什么的进程,先看lsof输出里进程名,再判断能不能重启。
文件被删除但空间不释放的问题在日志类服务、数据库、消息队列上特别常见,尤其是一些不会自动logrotate的进程,日志被手动删了但 fd 还握着,磁盘空间凭空消失几十 G。
4.2 根文件系统保留块被算进“已用”里
我用df -h查看/分区时,经常看到已经用了 95% 以上,但du -h --max-depth=1 /把所有目录加起来也没那么大,中间就差了那么几个 G。
这里面的主要原因之一是 ext4 等文件系统默认会预留 5% 的空间给 root 用户,用来防止系统因为空间耗尽而无法正常启动。这 5% 的保留块在df里是算在“已用”里面的,但du是完全感知不到这部分空间的。所以当你的根分区使用率显示 97%,但du统计的总和只有 92% 左右,不用太惊讶,这 5% 是文件系统层面的保留。
如果你确实需要释放这部分空间,用下面的命令调整保留比例:
tune2fs -m 1 /dev/sda1但不建议在生产服务器上随意调整,尤其是根分区。这 5% 空间在极端情况下是救命的,数据盘的保留比例调低一些倒是无妨。
4.3 挂载点、硬链接和稀疏文件的影响
再补充几种导致du和df不一致的情况。
挂载点嵌套是最常见的误判来源。你明明清理了/data下的一堆文件,/data所在分区空间却没变化,那是因为/data其实是一个独立挂载的分区,你在/data下清理的文件并不属于你df -h时看的那个分区。
硬链接会让du重复计算。同一个 inode 的同一个文件被硬链接到多个目录路径下,du在遍历每个路径时都会把这份文件的大小加一遍,导致总体统计结果比真实占用偏大。统计时若想按真实文件系统占用算,可以用-P参数不跟随符号链接,但硬链接本身造成的重复统计没有参数能完全规避。
还有一个容易忽略的是 NFS 挂载目录的st_nlink显示异常。某些网络文件系统上du会遍历到无穷无尽的目录循环,直接导致du卡死甚至报错,所以遇到 NFS 目录建议用du -x或者直接跳过,不要硬跑。
稀疏文件的例子:一个 10G 的文件,实际磁盘占用可能只有 1G,因为文件内部有很多空洞。du统计的是实际分配的块,ls -lh显示的是逻辑大小,差距巨大是正常的,你不要疑惑是不是统计错了。
5. 更高效的工具与实用技巧
5.1 用 ncdu 做交互式排查
如果你完全不想一层层敲命令,或者排查的是一个几百 G 的大目录,用ncdu体验会好很多。它是基于ncurses的交互式磁盘占用分析工具,安装特别简单:
# Debian/Ubuntu apt install ncdu # CentOS/RHEL yum install ncdu然后直接执行:
ncdu /var它会先把目录结构扫描一遍,然后进入交互界面,直接按大小排序展示所有子目录和文件。你可以用上下键移动,按回车进入子目录,按d删除当前项,按g重新扫描。这个工具在做临时上台机器快速排查时确实很顺手,所见即所得。
不过ncdu也有短板:它默认不会扫描挂载点之外的文件系统,对于跨分区的目录结构你需要在启动时加参数或者分多次扫描。另外它扫描大目录时耗时会比直接du -h --max-depth=1长一些,因为它是完整遍历所有子目录。如果只是想快速看一级目录大小,我还是推荐命令行du + sort的组合。
5.2 常用别名与一键脚本
排查的空间问题时,命令敲多了就希望能少敲几个字。我习惯在自己的 shell 配置里加几个别名,这里直接分享给你:
alias dud='du -h --max-depth=1 2>/dev/null | sort -hr' alias duf='du -sh 2>/dev/null' alias dfh='df -hT'这样每次排查只需执行:
dud /var dud /home dfh如果你经常需要在不同服务器上处理磁盘问题,我建议写一个简单的一键定位脚本,比如下面这样的bigfind.sh:
#!/bin/bash # 使用方式:./bigfind.sh /var TARGET=${1:-/} du -h --max-depth=1 "$TARGET" 2>/dev/null | sort -hr | head -20 echo "=== 文件系统使用情况 ===" df -hT "$TARGET"这个脚本会先列出指定目录下最大的 20 个子目录,再显示对应分区的整体情况,一次执行就能同时完成“目录内定位”和“分区整体判断”两个任务。
5.3 用 --exclude 和 -t 过滤掉不关心的目录
在实际的日志服务器上,最容易占空间的通常是固定路径,比如/var/log、/tmp、某个应用缓存目录。如果你只是想找出那些不常规的大目录,可以用--exclude把已知的大目录跳过:
du -h --max-depth=1 / --exclude=/proc --exclude=/sys --exclude=/data --exclude=/var/cache 2>/dev/null | sort -hr | head -20这样可以把旅行加速的重点放在还没排查过的目录上。另外还有一个-t参数只显示大于指定大小的目录,比如:
du -h -t 1G / 2>/dev/null | sort -hr只输出超过 1G 的目录,对某些磁盘容量超大的服务器来说也能减少噪音。
5.4 针对日志目录的特殊排查技巧
日志目录是磁盘占用的重灾区。它的特点是子目录多、文件名带日期、且单个文件可能动辄几十 G。排查日志目录时我更喜欢直接列文件而不是列目录:
ls -lhS /var/log/*.logls -lhS可以直接按文件大小从大到小列出所有日志文件,比先du再逐个看文件名更直观。但在日志目录里发现有超大日志文件时,并不建议直接对这个目录做rm操作。正确做法是确认该日志对应的服务,优先考虑:
# 查看日志服务是否支持配置 max size 和 rotation # 例如 rsyslog 的配置文件 /etc/logrotate.d/ # 针对某个具体 log 可以手动触发 logrotate logrotate -f /etc/logrotate.d/rsysloglogrotate可以在保留最新日志的前提下压缩和轮转历史日志,避免磁盘空间被一个单调增长的日志文件彻底占满。在日志文件还在增长的场景下,手动rm只是治标不治本,重点是把轮转策略配置好。
6. 常见问题速查表与实操心得
6.1 高频问题与解决方案
| 现象 | 可能原因 | 排查命令 | 处理建议 |
|---|---|---|---|
du -sh 某目录结果很大,但df -h分区明明还很空 | 该目录在挂载的其他分区上,或存在跨文件系统的目录 | df -h 目录名、mount | grep 目录名 | 确认目录所在分区,使用du -x跳过其他文件系统 |
删除了大文件,df -h可用空间没增加 | 文件被进程持有句柄 | lsof +L1 | 重启对应进程,或让进程关闭文件句柄 |
| 磁盘容量显示没满,但系统报 No space left on device | inode 耗尽 | df -i | 统计小文件数量,迁移或清理大量小文件所在目录 |
du统计所有目录总和,比df已用量小很多 | 文件系统保留块、已删除未释放空间、隐藏文件被遗漏 | tune2fs -l /dev/sdaX、du -sh /.* | 根据情况处理,删除隐藏目录或检查保留块 |
ncdu显示某目录非常大,但du -sh显示不大 | 稀疏文件或符号链接循环 | ls -lh、ls -l检查 inode | 根据文件类型对应处理 |
/proc、/sys目录不能直接 du | 虚拟文件系统 | 排除这两个目录 | 正常现象,不要尝试清理 |
6.2 我踩过的一些坑和总结的经验
踩坑经历先说说。有次帮同事排查一台编译机,磁盘 100% 满,所有常规命令都查不出哪里大。我du --max-depth=1逐层看,每个目录都 正常,但加起来才用了不到 60%。最后用lsof +L1才发现是一个被删除的编译临时文件,被某个还开着的进程一直占住,足足 40G。从那以后我排查磁盘问题一定会把lsof +L1纳入标准流程,而不是最后实在查不出来才想到。
还有一次是磁盘没满,但某个服务频繁宕机,查了半天才发现是/var目录下有一个隐藏的.cache目录被程序无限写入小文件,把 inode 耗尽了。所以我现在df -i也基本是必查项。
给几点个人的实操建议:
第一,没有把握不要删文件。宁可先du确认内容,看到路径名里带tmp、cache、log等明显标识,再根据自己对业务的了解决定是否清理。
第二,清理大日志时,推荐truncate而不是rm。对正在写入的日志文件执行truncate -s 0 文件名,能把文件清空但不改变文件路径和打开的文件句柄,相比rm后重新创建要优雅得多,尤其是服务进程并不支持动态重新打开日志文件的情况。
第三,谨慎使用rm -rf。每次敲rm -rf之前,我都强制自己先cd到绝对路径再操作,不要用相对路径,不要带通配符的模糊匹配去删。磁盘满了很急,但越急越要冷静。
第四,如果你的服务器有监控系统,建议对根分区和几个常用分区设置空间使用率告警,例如 85% 警告、92% 紧急,避免每次都是被动的“磁盘满了才发现”。
这个话题说到底,就是把du、df、lsof这三板斧用好,再配合一层层下钻的排查思路。我不太习惯写那种华丽的技术总结,只分享一个最朴素的心得——遇到磁盘问题别慌,先看分区,再看目录,最后看文件句柄,按这个顺序走,大多数问题都能很快定位。下一次你再看到磁盘告警的时候,直接按这篇文章的流程操作就行。