有人在服务器上磁盘写满导致服务崩溃,才想起自己连查看目录大小都不熟练;也有人刚把虚拟机镜像拷到新机器,发现磁盘不够用,却不知道究竟是哪个目录吃掉了空间。如果你也处于这种状态,du命令值得花半小时认真搞明白。作为 Linux 系统里最实用的容量分析工具,du的职责很简单:递归统计文件和目录占用的磁盘块,告诉你每个目录到底吃了多少空间。这篇内容就把du的常用参数、背后原理和真实场景里的排查套路完整过一遍,适合刚接触 Linux 的同学,也适合平时只用ls -lh、遇到磁盘告警就手足无措的运维入门者。我会从最基础用法讲起,一直到实际服务器上的排查案例,全程配上解释和踩坑记录,你照着敲就能上手。
1. 先搞清楚 du 在解决什么问题
1.1 磁盘空间告警时,你最需要的不是 ls 而是 du
很多人习惯用ls -l看文件大小,但这个方法在排查目录占用时几乎没用。ls -l显示的单个文件逻辑大小,不包含文件系统元数据、间接块等额外开销,而且面对成千上万个小文件,ls根本算不出目录总占用。比如你在根目录执行ls -l /,只能看到每个一级目录各自列出的条目,压根不知道/var下面那几百个日志文件加在一起占了多少。这就像你看钱包里每张钱的面额,却不知道口袋里总共多少钱,一张一张数既累又算不准。磁盘告警的场景里,运维需要的是一眼看清“哪个目录最大”,这个任务只有du能干。
du的全称是 disk usage,它会递归遍历指定目录下所有子目录,把每个文件的块数累加起来,最终输出目录实际占用的物理磁盘空间。它的输出单位默认是 KB,加上参数可以变成人类可读的 MB、GB。理解这一点后,你就能明白du -sh /var为什么能帮你快速定位:它会统计/var下所有文件的真实磁盘占用,然后以简练的格式输出一个总数字,这个数字才是你真正要跟磁盘告警阈值对比的数据。
对比之下,df命令负责的是另一个维度。df看的是整个文件系统的已用空间和可用空间,反映的是磁盘分区的总体状态,比如/dev/sda1 占用 80%;du则把视角拉近到某个目录树内部。一个管总览,一个管拆解,两兄弟配合起来才能完整回答“磁盘满了”这个问题:先用df -h确认哪个分区满了,再用du -sh *在这个分区里揪出最大目录。
1.2 du、ls、df 的分工:记住这张对比表
不少面试题喜欢问这三者的区别,其实在实战里它们的定位非常清晰。我整理了一份对照表,按“对象-任务-典型命令”的维度区分,这样记忆负担最小:
| 命令 | 统计对象 | 核心用途 | 典型输出示例 |
|---|---|---|---|
ls -lh | 单个文件 | 查看文件逻辑大小,不包括块开销 | -rw-r--r-- 1 root root 1.2G app.log |
du -sh | 目录及其子目录 | 递归统计目录实际占用磁盘块 | 1.2G /var/log |
df -h | 文件系统/挂载点 | 查看分区使用率、剩余空间 | /dev/sda1 20G 16G 4.4G 79% / |
这里有个非常容易踩坑的细节:ls里看到的1.2G和du里看到的1.2G含义其实不一样。文件系统按块分配空间,一个 1 字节的文件也可能占 4KB 块,如果文件系统里成千上万个小于 4KB 的文件,ls加起来的总逻辑大小会远小于du算出来的实际占用。所以面试如果问“为什么 du 比 ls 加起来的大”,答案不是 du 错了,而是它俩度量的根本不是同一个东西:一个是文件逻辑长度,一个是磁盘物理用量。
2. du 命令核心参数拆解:每个参数背后的设计逻辑
2.1 基本用法:不带参数时到底输出什么
直接在终端敲du不带路径,默认统计当前目录下所有子目录的磁盘使用量,层层递归,直到叶子目录为止。你可能会看到一大堆数字,每个数字前面是目录相对路径,比如:
$ du /var/log 4 /var/log/apt 12 /var/log/nginx 384 /var/log/journal 532 /var/log输出的第一列数字单位是 KB(1024 字节),不是字节。这是初学者最容易误读的地方——du裸奔时以 1024 字节为固定块单位,看到532代表 532KB,不是 532 字节。这个输出形式最大的问题是信息量太大:如果目录层级深、子目录多,会刷出几百行结果,根本没法一眼抓重点。
所以实战中几乎没有人直接用裸du,要么加上-s汇总,要么加-d限制层级,要么加--max-depth控制递归深度。这里建议你从du -sh这个组合开始记:-s是 summarize 的缩写,只输出总计;-h是 human-readable,自动换算成1.2G、356M这种单位。这两个参数是排查磁盘问题时最高频的组合,没有之一。
2.2 -s、-h、-d、-m、-k:各参数适用场景对照
表格能帮你快速建立参数全貌。我把最常用的参数按“输出形式”和“递归深度”两个维度归了个类:
| 参数 | 含义 | 适用场景 | 实战示例 |
|---|---|---|---|
-s | 只显示总计 | 快速确认目录总大小 | du -sh /opt |
-h | 人类可读单位 | 配合 -s 或 -d 使用 | du -sh /var/log |
-d N | 递归深度为 N | 控制输出层级,避免刷屏 | du -h --max-depth=1 / |
-m | 以 MB 为单位 | 脚本换算出具体数值 | du -sm /home |
-k | 以 KB 为单位 | 默认单位,手动强制指定 | du -sk /root |
-x | 不跨越文件系统 | 跳过挂载点如 /proc、/sys | du -shx / |
-d和--max-depth是同义的,GNU coreutils 版本里两个写法都支持。设置--max-depth=1时,du 只会统计第一级子目录各自的总大小,不会往下继续递归输出,这对定位“根目录下面哪层目录最大”特别有用,是排查磁盘问题时我最常用的深度控制方式。-x参数更适合你不希望 du 跑到挂载点里去的场景,比如根目录上挂了/data独立分区,直接du -sh /会连/data一起统计,加上-x就可以跳过其他文件系统。
2.3 排序和排除参数:别让无用信息占据视野
实际排查时,du裸输出依然是“信息过载”的,因为日志目录、临时文件目录、系统快照目录往往会混在一起。这时候两个参数能救命:--exclude和管道搭配sort。
--exclude用于排除不需要统计的路径模式。比如你在统计/home时想把隐藏的缓存目录过滤掉:
du -sh --exclude=".cache" /home/*这条命令会列出/home下每个用户目录的大小,但跳过所有名为.cache的子目录。这个模式是基于文件名通配的,写目录名即可,不必写完整绝对路径。
如果目录数量多,手动一个个看数字显然低效,管道接sort -h能让结果从大到小排列,直接扫一眼头部就能锁定罪魁祸首:
du -sh /home/* | sort -hrsort -h专门解决人类可读单位的问题,比如1.1G会正确排在900M前面,而不是按字典序把1.1G排在900M之后。如果你不用-h而是输出的纯数字 KB,也能用sort -rn按数值倒排。我的习惯是统一du -sh输出的路径列表用sort -hr,因为带单位的输出一眼能看出量级差距,比纯数字直观太多。
2.4 实用组合:一条揪出某目录下最大子目录
讲了这么多参数,直接上一个在生产环境里高频复用的组合命令。比如我接到告警说根分区满了,第一个要查的是根目录下哪个子目录最大:
df -h / cd / du -h --max-depth=1 -x / 2>/dev/null | sort -hr | head -20解释一下这条命令的构成:df -h /先确认根分区是否真的满,du -h --max-depth=1 -x /统计根下面第一层目录大小并排除挂载点,2>/dev/null屏蔽掉无权访问文件的 Permission denied 报错,sort -hr按大小倒序,head -20只保留前 20 行。这样一条命令执行完,磁盘空间去哪了一目了然。
3. 实战演练:用 du 解决几个真实磁盘问题
3.1 场景一:根分区 100% 占满,揪出罪魁祸首
我模拟过很多次这样的故障:服务器突然告警,df -h /显示/dev/mapper/ubuntu-root已用 100%。此时不要慌,按照下面三步快速走完一轮:
第一步,确认总占用概况。du -h --max-depth=1 -x / | sort -hr | head -10,这一步会得到类似这样的输出:
22G /var 8.5G /usr 3.2G /home 1.2G /opt 940M /root一眼看到/var明显异常,占到了 22G,而正常系统这个目录一般是几 G 级别。锁定二级目录优先级从/var开始,不用继续浪费时间翻其他目录。
第二步,深入异常目录。cd /var && du -h --max-depth=1 | sort -hr | head -10,快速定位到/var/log或/var/lib。比如结果显示:
18G /var/log 3.1G /var/lib第三步,继续往下钻。du -h --max-depth=1 /var/log | sort -hr,发现/var/log/journal占了 15G,这个往往是 systemd-journald 日志累积导致的,按需清掉旧日志即可。整套流程下来,从接到告警到定位根因不超过两分钟,全部靠du的参数组合完成,不需要安装任何额外的图形化工具。
3.2 场景二:找出当前目录下哪些大文件占空间
du 虽然擅长统计目录,但偶尔也需要定位某个超大文件。这时的思路是:先列出当前目录下所有文件的大小,再用sort倒排。可以用两条命令实现:
ls -lhS | head -20 find . -type f -size +500M -exec ls -lh {} \;第一条ls -lhS按文件大小从大到小列出当前目录的文件,-S就是 size 排序;第二条则用find按阈值找超过 500MB 的大文件。不过如果目录里包含大量子目录,这两条命令并不能覆盖整个目录树。想对当前目录树做全局排查,还是建议配合du:
du -ah . 2>/dev/null | sort -hr | head -20-a参数让 du 输出目录之外的所有文件条目,后面接sort -hr倒排,就能在一堆输出里找到体积最大的几个文件。之前我排查过一个应用服务器,docker 容器产生了 80G 的 json 日志文件,就是用这条命令从海量输出里一眼挑出那个最大的日志文件,精准清理掉的。
3.3 场景三:用 du 验证 NFS 或挂载磁盘的空间消耗
服务器挂载了 NFS 网络磁盘或独立数据盘后,经常出现这样的怪事:df -h显示这个挂载点剩了很多空间,但du -sh /data算出来占用却非常大,两个数字对不上。此时不是谁“说谎”了,而是 du 和 df 统计的口径不同:df看的是文件系统超级块里的已用块,是全局视角;du则扫描目录项逐一累加文件大小,会漏掉已删除但仍被进程占用的文件、套接字文件、以及某些特殊文件系统。
这类场景里最典型的坑就是“文件已删除但空间未释放”。一个大日志文件被rm删了,但仍有进程持有句柄,此时df依然显示该空间被占用,du却怎么也统计不到这个文件。排查方法是用lsof | grep deleted找出还在占用空间的文件,然后重启对应进程或服务释放句柄。这个案例恰恰说明,du 和 df 在特定场景下需要用组合拳,而非单靠某一个命令下结论。
4. 高频报错与排查技巧实录
4.1 “Permission denied” 和 “No such file or directory” 的应对
用du -sh /这种命令扫描根目录时,控制台常常刷出一堆du: cannot read directory 'xxx': Permission denied。这不是命令坏了,而是当前用户没有读取某些系统目录的权限,比如/proc下的子目录、其他用户的家目录。处理起来非常简单,2>/dev/null屏蔽掉错误输出,不影响结果统计。
但这里有个细节值得注意:屏蔽错误不等于跳过统计。du 遇到无权访问的目录时,会跳过该目录且不将其计入总大小,也就是说du -shx /的输出其实会“低估”实际占用。如果你是 root 用户,这种问题基本不存在;如果是普通用户,需要确认扫描范围是否有遗漏。我在排查中会先看 stderr 里报错路径的数量,如果特别多,就对报错路径单独授权或换 root 执行,避免统计失真。
4.2 硬链接文件导致重复统计的真相
du在统计时会为每个目录项单独计算文件大小,如果同一个 inode 被多个目录硬链接引用,du 会把每个链接都算一遍。举个极端例子,你创建了一个 1G 的文件,然后为它创建了 3 个硬链接,du统计这些目录时可能会显示总共 4G,但实际物理磁盘只消耗了 1G。
这个现象在备份系统、快照目录里尤其常见,会让 du 的统计结果非常大,容易吓人一跳。排查硬链接的简单方法是用ls -l看文件的链接数(第二列数字),如果大于 1 就代表存在多个硬链接。不过大多数日常场景里,硬链接数量有限,对整体定位影响不大,无需过度担心。真正的重点是:du 输出的“大”不代表磁盘真的消耗了这么大的物理空间,它统计的是逻辑目录树视角下的占用总量。
4.3 du 统计速度极慢的优化思路
目录里文件数量达到百万级别,或者跨 NFS 网络磁盘扫描时,du会变得非常慢,因为需要逐个 stat 文件。以下几个优化手段实测有效:
第一,缩小扫描范围。先明确最可疑的目录,别一上来就扫整个根目录。比如日志都在/var/log,就直接从/var/log开始,用--max-depth控制层数,层层递进。第二,使用-x跳过其他挂载点,避免 du 跑进 NFS、/proc等分区浪费时间。第三,排除明显不相关的目录,--exclude=/proc --exclude=/sys这种写法在扫描根目录时可以省去大量无意义的 inode 访问。
如果上述手段还不够满意,更彻底的办法是放弃 du,改用ncdu或dust这类工具。它们交互式展示大目录,按字节精确排序,扫描速度和实时反馈远胜原生命令。但对刚入门的用户,我仍然建议先熟练掌握 du,因为它不需要额外安装、任何 Linux 发行版都自带,是排查问题的保底手段。
4.4 面试视角:关于 du 的经典问题到底在考什么
热词里提到了 Linux 面试题,老实说 du 相关的问题在基础运维岗经常出现。最常被问的是:“du 和 df 区别是什么?”回答的关键是抓住“目录 vs 文件系统”的视角差异:du 自下而上统计目录树,df 自上而下读取超级块,二者在文件删除未释放、稀疏文件等场景下可能出现不一致。
第二个高频问题是:“du -sh 之后为什么还有空间被占用?”这考的其实是对已删除文件句柄的理解。标准答案是:用lsof +L1或lsof | grep deleted查看被删除但仍有进程打开的日志文件,然后重启对应进程或清空该文件。这两个问题平时看似简单,一旦故障真来了,扎实理解背后原理的人反应速度会快得多。
5. 我的几个真实体会与收尾建议
用 du 这几年,最想提醒新手的一件事:不要一上来就du -sh /,那样只得到一个毫无区分度的总数字,之外什么都得不到。真正有效的排查是逐层下钻,配合sort和xargs,把目录大小排序后才能找到异常项。比如下钻时可以玩出这种复合命令:
find /var -maxdepth 2 -type d -exec du -sh {} \; | sort -hr | head -30这条命令会列出/var下两层目录里最大的 30 个目录,既保证深度,又不会刷屏,适用于模糊定位阶段。
第二个体会和习惯相关:我在一台长期使用的服务器上维护了一个别名文件,里面放了几个高频 du 用法,比如查看当前目录下最大顶层目录的du -h --max-depth=1 | sort -hr,以及查看当前目录下所有大文件的du -ah | sort -hr | head。这样接到告警时手不用停下来想命令,直接敲 alias 就能执行。你要是也想偷懒,可以把这两行写进~/.bashrc:
alias dud='du -h --max-depth=1 | sort -hr' alias duf='du -ah | sort -hr | head -30'最后再说一个容易遗忘的细节:du 在 32 位系统上遇到超过 2GB 的文件时,在没有加-h或-m参数的情况下,输出数值会溢出变成负数或异常值。虽然现在 64 位系统已成主流,但在嵌入式设备或老内核的机器上仍有踩坑可能。这个问题我遇到过两次,容易让人误以为工具坏了,实际上是整型溢出,加上合适的人类可读单位参数即可解决。
工具本身很简单,真正值钱的是排查思路。掌握了 du 的常用参数和组合玩法,下次磁盘告警再也不会手忙脚乱。先用df -h看分区,再用du -h --max-depth=1 -x下钻,配合sort倒排,最多三次递归就能定位到问题目录,这套流程在各种服务器环境里都行之有效,值得收藏备用。