今天聊一下 Linux 下最常用的磁盘占用排查命令:du。不管你是在生产服务器上看到报警“磁盘空间不足”,还是本地开发环境莫名撑爆了根分区,总得先搞清楚哪个文件、哪个目录把空间给吃掉了。du 就是干这个的。
这个命令的核心能力是递归统计文件和目录的磁盘占用,配合排序和排除参数,可以快速锁定“到底是谁占了我的硬盘”。它和 df 这种查看分区整体使用率的工具完全不同,前者是“从下往上逐一把账目算清”,后者是“直接看总额还有多少余额”。这篇内容我会从最基础的用法开始,逐步深入到实战排查场景,顺带把我在运维过程中踩过的坑、试过的技巧都写出来,适合刚接触 Linux 的初学者,也适合已经会用但想系统梳理的运维同行。
我在实际使用中发现,du 这个命令看起来很“简单”,但它有一堆容易忽略的细节:为什么统计出来的空间大小和 df 对不上?为什么删除文件后磁盘空间没有释放?为什么小文件明明只有 100 字节却显示占用了 4K?这些问题不弄明白,排查磁盘问题时很容易走弯路。下面我从头到尾讲清楚。
1. 为什么需要 du 以及它和 df 的根本区别
很多人最开始分不清 du 和 df,以为两个都是看磁盘的命令,随便用哪个都行。实际上它们的统计口径完全不同,排查思路也因此不一样。
1.1 一句话理解 du
du 的全称是 disk usage,它的工作机制是进入你指定的目录,逐层递归地访问每一个子目录和文件,从文件系统读取每个对象的元数据里的块数信息,然后一层层累加汇总,最后输出该目录树的总占用。
你可以把它想象成:你想知道家里有多少存款,但银行给你的是总额,你反而要自己拿个小本子把每张银行卡、每笔理财、抽屉里的现金逐项记下来,最后加总。如果你只想知道某个目录装了多大,用du -sh /路径一条命令就搞定了。
1.2 du 和 df 的差异
df 的全称是 disk free,它查看的是整个文件系统、整个分区的使用情况,比如/dev/sda1挂载在/,总共 100G,已用 80G,剩余 20G。df 不关心具体是哪个目录用了这 80G,它只知道整个分区层面还剩多少。
du 则相反,它深入到目录内部,把每个文件占用的实际磁盘块数统计出来,最后汇总。所以排查“哪个目录把空间占满了”的正确答案一定是用 du,而不是 df。
我用一个直观的对比表来说明:
| 维度 | df | du |
|---|---|---|
| 统计对象 | 文件系统/挂载点 | 目录和文件 |
| 统计方式 | 读取文件系统超级块信息 | 递归遍历目录树 |
| 能否定位目录 | 不能,只显示整体 | 能,逐级列出 |
| 常用场景 | 确认哪个分区满了 | 确认哪个目录或文件占空间多 |
| 返回速度 | 快,毫秒级 | 慢,取决于目录大小 |
1.3 磁盘空间去哪儿了:先从系统层面排查
如果你接到了磁盘告警,我的建议是先用df -h看一遍所有挂载点,确认到底是哪个分区满了,同时也看一下当前工作目录所在文件系统是否是那个满的分区。这里有个经常出现的误区:你在/data下执行du -sh,结果发现占用很小,但df -h却显示/data所在磁盘已经 100% 满了,这种差异往往不是算错了,而是有隐藏的占用,后面我会专门讲这个问题。
2. 基础用法:从最简单的 du -sh 开始
我平时几乎天天用du -sh,因为它的输出最干净,只显示汇总大小,不打印一大串子目录路径。对于快速判断一个目录的整体体量,这是最顺手的方式。
2.1 查看当前目录总大小
直接在目录里执行:
du -sh .输出结果可能是:
1.2G .-s表示 summary,只输出总计;-h表示 human-readable,以 K、M、G 等单位显示。如果你不写-h,输出会是一堆以 KB 为单位的数字,比如 1258292,看起来非常吃力,所以我建议在任何地方都习惯性加上-h。
2.2 查看指定目录
查看特定路径时,直接跟上目录名即可:
du -sh /var/log du -sh /home/user du -sh /var/log /home/user /tmp最后一条命令会一次性输出多个目录各自的占用,后面我会结合排序命令找出最大的那个。
2.3 单位选择:不要被默认的 4K 弄糊涂
-h是自动选择合适单位,但在脚本或者是需要统一口径时,可能需要固定单位:
-k:以 KB 为单位-m:以 MB 为单位-B 1:以字节为单位--si:使用 1000 进制而不是 1024 进制,比如 1G 表示 1000M,而不是 1024M
很多人没注意到-h的“人类可读”并不是标准意义上的 1024 进制,GNU du 默认以 1024 为换算单位。如果某些软件或者云平台要求按 1000 进制计算,就用--si控制。
另外,细心的朋友可能会发现,一个只有 10 字节的小文件,du -h却显示 4.0K。这是因为磁盘按块分配空间,默认块大小是 4096 字节,一个文件至少要占一个块。所以 du 统计的是“实际占用的磁盘空间”,不是文件内容的“逻辑大小”。如果你要看书面的文件大小,用ls -l;如果要看占了多少磁盘块,用du。想单独看逻辑大小时可以加--apparent-size参数:
du -h --apparent-size somefile这个参数在判断“一堆小文件实际占用”时很有用,因为稀疏文件、压缩文件、以及大量小文件场景下,逻辑大小和实际占用往往差异巨大。
2.4 隐藏文件与通配符细节
一个非常容易被忽略的坑是:du -sh *这个写法默认不会统计以点号开头的隐藏文件。如果你在某个目录下执行:
du -sh * | sort -h发现所有输出加起来和du -sh .对不上,往往就是隐藏文件被漏掉了。比如用户的.cache、.local、.config这类目录,经常在不知不觉间积攒了十几个 G。
想包含隐藏文件,可以这么写:
du -sh .[!.]* * 2>/dev/null | sort -h或者用更稳妥的方式:
du -sh ./* .[!.]* 2>/dev/null | sort -h这里的.[!.]*会匹配.foo这种以点开头但第二字符不是点的项,2>/dev/null是为了过滤无权限访问时的错误信息。如果你不想要这种通配符的绕法,更推荐直接用下一节讲的--max-depth参数。
3. 进阶:统计子目录占用与排序
了解基础用法后,真正的高频场景是:一个大目录下有很多子目录,我要快速找出谁最大。这就需要控制 du 的递归深度,再把结果排序。
3.1 用 max-depth 控制层级
较新版本的 GNU du 支持--max-depth=N,只向下统计 N 层目录。我常用的写法是:
du -h --max-depth=1 /home等价于:
du -h -d 1 /home这条命令会列出 /home 下每一个直接子目录的大小,以及 /home 本身的总大小。--max-depth=1是最实用的参数,它避免了一屏刷不完的尴尬,同时又能把问题直接定位到一级目录。
如果想要看到两级:
du -h --max-depth=2 /home还是那句建议:先用 depth 1,锁定嫌疑目录,再进入子目录继续 depth 1 排查,而不是一次性 depth 3 输出几百行。
3.2 对结果排序找出“大户”
光有数字还不够,得把最大的排在最前面。du 本身不带排序功能,我们需要把它和 sort 组合起来:
du -h --max-depth=1 /home | sort -h -r这里关键点是sort -h,表示按照人类可读的数字大小排序,而不是按字典序。如果不加-h,你会看到9G排在100M前面,因为字母序里 “9” 大于 “1”。-r表示降序,最大的在最前。
更稳妥的写法是:
du -h --max-depth=1 /home 2>/dev/null | sort -h -r | head -20只保留前 20 行,避免输出过多干扰判断。如果你所在环境是老版本 sort,不支持-h,也可以用:
du -k --max-depth=1 /home | sort -n -r | head -20先统一以 KB 为单位排序,看够了再转成人类可读数字,不过系统里一般都会支持-h。
3.3 用 du 配合 find 处理特定文件
有时候你不需要统计整个目录树,只需要找出某些特定类型的大文件。比如查找当前目录下所有超过 100M 的日志文件:
find /var/log -type f -size +100M -exec du -h {} \;这里的find负责筛选大文件,du -h再负责输出每个文件的实际磁盘占用。这种组合在排查“某个目录总大小正常,但某个单文件异常庞大”时非常有效。还可以配合xargs批量处理:
find /home -type f -size +500M -print0 | xargs -0 du -h | sort -rh-print0和xargs -0的目的都是为了正确处理文件名里的空格和换行符,这是我踩过坑之后养成的习惯。如果文件名中有特殊字符,直接find ... | xargs du轻则统计错误,重则命令失败或误删文件。
3.4 排除日志或缓存目录
在实际服务器上,总有一些目录我们心里清楚它一定很大,暂时又不想看,比如/var/log下的历史日志,或者某个程序留下的缓存。为了避免它们干扰排查,可以用--exclude参数:
du -h --max-depth=1 --exclude=/var/log /var或者用通配符排除所有.git目录:
du -h --max-depth=2 --exclude='*.git' /path/to/project还有一个特别实用的场景:备份目录。很多项目在代码目录里有 node_modules、vendor、dist 等巨型目录,逐个统计时会刷满屏幕。这时候先排除掉它们,更快地看到真正需要手工分析的部分。等到定位完业务数据后,再单独去检查被排除的目录。
4. 实战场景:从“磁盘满”到“清理完成”的完整排查流程
讲了这么多参数,不如完整走一遍真实场景。假设我接到一个任务:某台服务器/分区 100%,网站开始报错,服务起不来。我需要快速找出是谁把磁盘占满了,并安全地做出处理。
4.1 场景初始化
先说明一下,我使用的系统是常见的 Linux 发行版,GNU 工具集。为了演练,我提前在一个测试目录里制造了“案发现场”:一个大日志目录、一个缓存目录、一些隐藏文件,还有一个被进程删除但仍然被占用的文件。
实际排查时第一步永远是:
df -h输出大概长这样:
Filesystem Size Used Avail Use% Mounted on /dev/sda1 98G 98G 3.4M 100% / tmpfs 7.8G 0 7.8G 0% /dev/shm ...确认根分区满了,下一步就该用 du 定位具体路径了。
4.2 第一轮:du 自上而下锁定目录
在根目录下执行:
du -h --max-depth=1 -x / 2>/dev/null | sort -h -r | head -10注意我加了-x,这个参数的意思是统计时不要跨越文件系统边界。因为/proc、/sys、/dev等虚拟文件系统在 du 眼里如果递归下去又慢又没意义,还可能统计出和物理磁盘无关的数字。-x会让 du 只统计根文件系统上的目录。
输出可能是:
95G /opt 1.8G /var 12M /etc ...一下子就锁定了/opt是最大的疑点。
4.3 第二轮:进入目标目录继续细分
紧接着:
du -h --max-depth=1 /opt 2>/dev/null | sort -h -r | head -20输出:
92G /opt/application 2.1G /opt/backup 800M /opt/software ...继续向/opt/application内部看:
du -h --max-depth=2 /opt/application 2>/dev/null | sort -h -r | head -20最后定位到/opt/application/logs/下的access.log.20240101等历史日志文件,总大小 90G。这个逐层缩小的过程,就是标准的 du 排查法:从根开始,每次只往下看一层,永远把最大目录作为下一轮的入口。
4.4 第三轮:细看文件级占用
当目录下一层已经全是文件、没有子目录时,--max-depth就不好用了,可以用-a参数列出所有文件:
du -ah /opt/application/logs 2>/dev/null | sort -h -r | head -10这里-a表示列出目录树中每个文件的占用,而不只是目录汇总。输出里单个文件的大小一目了然,直接就能判断哪个日志该归档,哪个缓存该清。
4.5 清理与验证
清理前强烈建议先和业务方确认哪些文件可以删除。如果只是一些滚动日志,常见的做法是压缩归档或者用truncate清空而不是直接rm。比如:
truncate -s 0 /opt/application/logs/access.log.20240101清空文件之后,再执行:
df -h /确认可用空间回升。如果还有报警,继续用 du 下一层找。
4.6 补充:被删除但未被释放的文件
这是排查磁盘空间时最反直觉的事:你用 du 统计某个目录,发现占用不大,但df -h还是满的。这时候十有八九是某个进程打开了文件,文件被rm删除,但进程还持有该文件描述符,所以文件的数据块并没有真正释放。
排查方法不是用 du,而是用 lsof:
lsof +L1或者:
lsof / | grep deleted这个命令能列出已经被删除、但仍然被进程打开的文件路径和 PID。看到结果后,和进程负责人确认是否可以重启该服务或重载配置。服务重启后,文件描述符释放,磁盘空间才会真正回来。很多新手在这个问题上卡上一整天,其实原因就这么简单。
5. 常见问题与避坑清单
把平时遇到的典型问题整理成一张速查表,方便翻阅:
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| du -sh 和 df 对不上 | 目录中统计时跳过了挂载点/虚拟文件系统,或有文件被删除但进程未释放 | 加上-x,再用 lsof 排查L1 |
| 小文件显示 4K、8K | 磁盘块大小,文件至少要占一个块 | 用--apparent-size查看逻辑大小 |
| 目录统计很慢 | 子目录太多、文件太多,或跨越网络文件系统 | 增加 exclude、限制深度、用-x |
| 去掉 . 后总和小于总量 | 隐藏文件被通配符漏掉了 | 使用[-a]*或--max-depth |
| 权限报错 Permission denied | 当前用户无权读取部分目录 | 加 sudo,或用 2>/dev/null 忽略 |
5.1 为什么 du -sh 显示的比 df 用的空间小
这是出现频率最高的问题。核心原因是在同一个分区上,可能有文件系统预留空间、已删除但未释放的文件、以及 du 默认跳过挂载点。为了排查,我会习惯性地用du -x -sh /和df -h /对比。如果仍然差很多,重点看 lsof 的输出。我遇到过的最夸张的一次是,根分区显示满,但整个根目录 du 加起来只有 20G,最后发现是某个服务删除了两个 30G 的日志文件,进程一直没重启,空间一直没释放。
5.2 大目录统计耗时怎么办
统计几千个文件还好,遇到几百万个文件的目录时,du 可能要跑几分钟。这时候有几个技巧:一是使用ionice降低磁盘 IO 优先级,避免影响线上业务:
ionice -c 2 -n 7 du -sh /data二是可以先把统计放到后台运行:
nohup du -h --max-depth=1 /data > du_result.txt 2>&1 &等它跑完再看结果,期间不需要阻塞终端。三是如果只是日常监控,不要频繁对整个根目录跑深度统计,建议用定时任务把统计结果定期输出到文件,需要排查时直接翻记录。
5.3 权限不足的处理
普通用户执行 du 时经常看到:
du: cannot read directory '/root': Permission denied这不会导致命令中断,只是跳过无权访问的目录,最终结果会比实际偏小。如果确实需要完整统计,就加 sudo 执行:
sudo du -sh /root如果不想被错误信息刷屏,可以在命令后面加2>/dev/null。但在排查阶段我建议不要完全屏蔽 stderr,因为你可能需要分辨哪些目录是因为权限无法访问,以免漏掉真正的“大户”。
5.4 软链接和硬链接的计算差异
du 默认不会跟随符号链接展开统计,也就是说如果你在一个目录下建了软链接指向/opt,然后在当前目录执行du -sh,这个软链接本身只占一点点空间,不会去统计/opt的内容。这是合理的设计,否则一个软链接可能导致重复计算和遍历环。需要强制跟随的时候,用-L:
du -h -L /path/to/symlink但生产环境里我基本不用-L,容易因循环链接导致结果重复或变慢。
硬链接则不同。多个硬链接指向同一个文件,磁盘空间只占一份。du 对硬链接的处理是,默认情况下同一个 inode 只统计一次,如果你用了-l参数才会把所有链接都分别计入。所以在普通场景下,du 是符合直觉的“实际磁盘占用”口径。
5.5 结果中的 . 表示什么
执行du -h --max-depth=1时,最后一行往往是一个点:
1.2G /home 12M /home/user 1.2G .这个点在 du 输出里代表当前统计的根目录自身,也就是整个目录树的总大小。排序时如果不小心,可能把这一行混进子目录里。如果你只想看子目录,可以过滤掉它:
du -h --max-depth=1 /home 2>/dev/null | grep -v '/home$'不过在实战中我通常保留这一行,因为直接就能看到总占用,方便和 df 做对比。
5.6 du 与 inode 耗尽的关系
最后补充一个很多人会混淆的知识点:磁盘空间满有两种情况,一种是容量满,另一种是 inode 满。du 只能看容量,看不出来 inode 是否耗尽。如果你的服务器明明df -h显示还有几十 G,但创建文件时却提示“No space left on device”,那很可能是 inode 不够了。
这时候应该用:
df -i /查看 inode 使用率。如果 inode 使用率达到 100%,即使磁盘容量还有很多,也无法新建文件。对于大量小文件堆积的目录,尤其是缓存目录、消息队列积压目录,经常会出现这种问题。排查 inode 大户可以用:
find / -xdev -printf '%h\n' 2>/dev/null | sort | uniq -c | sort -rn | head -20这条命令会统计每个目录下文件数量最多的前 20 个位置。这里面的-xdev和 du 的-x一个思路,只检查当前分区,避免遍历挂载点。
我在实际工作中已经养成了一个习惯:每次清理磁盘前,一定先记录当前的 du 明细到临时文件,清理后再次对比,确认空间恢复到了预期。这样既方便事后审计,也能避免误删导致的服务异常。du 这个命令看似简单,但把它的参数吃透、把统计口径理解清楚,排查磁盘问题就能少走很多弯路。