
半夜两点收到磁盘告警登录上去df -h一看根分区 100%。这种场景干运维的应该都不陌生。面对一个快满的根分区第一件事不是急着删文件而是先搞清楚空间到底被谁吃掉了。这时候 Linux 自带的du命令就是最顺手的工具。我见过不少新人在这一步直接对着目录rm -rf结果把自己坑惨了。du的全称是 disk usage作用就是统计文件或目录占用的磁盘空间是 Linux 查看文件或文件夹大小的首选命令。这篇文章我把du从参数到实战讲透包括它和df的分工、排查大文件的完整流程以及几个特别容易踩的坑无论你是刚学 Linux 的新手还是日常要处理服务器告警的运维都能直接拿来用。1. du命令到底是什么一次说清它和df的本质区别1.1 从名字说起disk usage到底在统计什么du的全称是 disk usage字面意思是磁盘用量。但它统计的并不是我们在 Windows 里看到的文件大小而是文件在磁盘上实际占用的块数。Linux 文件系统在存文件的时候是以块block为单位的常见的块大小是 4KB也就是 4096 字节。哪怕你创建一个只有 1 个字节的文件它在磁盘上也要占一个块也就是 4KB。反过来一个 4097 字节的文件要占两个块也就是 8KB。这个区别在什么时候会体现出来最典型的就是那些由海量小文件组成的目录比如 Redis 的持久化目录、npm 的缓存目录又或者 Kafka 的数据目录。这些目录里文件数量动辄几万几十万每个文件都有块对齐的损耗。你拿ls -l去算加起来可能只有 1GB但du出来往往是 1.5GB 甚至 2GB因为每个小文件尾巴上那点零头都被凑整成一个完整的块了。所以在 Linux 下面判断一个目录到底多大不能靠ls必须靠du。这是个很基础但很多人意识不到的点。ls -l显示的是文件的逻辑大小du统计的是物理占用的真实磁盘空间。对排查磁盘空间不足的问题来说后者才是我们真正关心的数字。1.2 du和df为什么两个命令的结果对不上经常有人问为什么df看到的使用量是 80GB我把目录加起来du一下只有 60GB是不是算错了其实两个命令的统计原理完全不同。dfdisk free站在文件系统层面直接读取文件系统超级块superblock里的元数据得到这个文件系统总共多大、用了多少、还剩多少。这个过程不遍历目录所以df的执行速度非常快一秒钟都不到。而du站在文件层面要从你指定的目录开始一层一层遍历整个目录树对每个文件调用stat获取占用块数最后累加起来。这个过程完全是实打实的遍历目录越大、文件越多耗时越长。因为统计口径不同du的总和比df小是很正常的原因主要有三个被删除但还被进程占用的文件这个文件在目录里已经看不到了但空间还没释放df会把它算进去du遍历目录树根本遇不到它文件系统预留块比如 ext4 默认会预留 5% 给 root这部分df会计入已用或预留du永远统计不到文件系统自身的元数据inode 表、日志journal、目录项这些开销都要占空间但都不在du的统计范围内。反过来如果du的结果比df还大通常是因为你把同一个挂载点下的多个目录重复统计了比如du了一个父目录又du它的子目录最后把重复的部分加了两遍。这个在写脚本的时候要特别注意。顺便说一句du和df的区别也是面试里特别喜欢问的一道题能把上面的原因讲清楚基本就过关了。1.3 什么场景用du什么场景用df先分清再动手干活之前先想清楚用哪个工具能省很多无用功需求用哪个原因文件系统还剩多少空间df -h秒出结果不需要遍历哪个目录占用了大量空间du按目录粒度定位某个具体文件占多大ls -lh或du -h单文件用ls更快磁盘没空间但目录加起来很小lsof L1结合 df找被删除但仍被占用的文件跨文件系统统计目录du -x排除挂载点只统计本文件系统这里尤其要说一下lsof L1。我遇到过一次很妖的问题df显示/已经满了但du从根开始把整个目录树加起来才用了不到一半。最后用lsof L1一查发现一个被误删的日志文件还在被 Java 进程握着文件虽然在目录里消失了空间却一直占着。这种情况在 MySQL、Redis、Nginx 日志这类长时间运行的进程上特别常见。排查目录不大但磁盘满了第一反应就应该是找 deleted 文件。2. 最常用的du参数组合从 -h 到 --max-depth 的实战解读2.1 入门三件套-h、-s、-d直接把du不带参数跑一下你会发现它把当前目录下每一级子目录都列出来了输出几百行而且数字全是裸的字节数看的人头皮发麻。实际工作中 90% 的场景只需要三个参数组合。第一个是-h--human-readable把字节数转成人类友好的单位比如 2.3G、45M、128K。这个参数基本没有理由不加除非你是在写脚本做数值运算需要得到精确的字节数做比较。第二个是-s--summarize只显示你指定目录的汇总大小不列出里面的子目录。du -sh是使用频率最高的组合用来快速看一个目录总共占了多少。比如du -sh /var/log du -sh ~ du -sh /data/mysql第三个是-d或--max-depth控制显示的深度。du -h --max-depth1 /var的意思是把/var下第一层子目录各自的大小列出来方便一眼看出哪一块最肥。这个参数在排查问题时是主力后面实战部分会反复用到。这三个参数单独用都很好理解但组合起来要记住一个原则-s是-d 0的等价写法两者不要同时用而-h任何时候都可以放心地和-s、-d一起加。2.2 进阶参数-a、-c、-x 与 --exclude把目录层级捋清楚之后你可能还想知道具体哪个文件最大。默认情况下du只输出目录的占用-a--all可以让它把目录下的每个文件也一并输出。比如du -ah /data会把/data下所有文件、目录的大小都打印出来配合sort就能直接找出最大的那几个文件。-c--total则会在输出的最后加一行总合计。这个参数适合一次统计多个目录的时候用比如du -sc /var/log /var/cache /var/lib/docker最后那行total就是这几个目录加在一起的总量不用自己拿计算器加。-x--one-file-system是写脚本时特别重要的一个参数。它让du在遇到挂载点时停下不跨文件系统继续统计。什么意思呢比如你执行du -sh /如果不加-xdu会把/proc、/sys、/dev这些虚拟文件系统也遍历一遍。这些目录看起来不大但遍历它们白白浪费时间而且某些虚拟文件系统里的内容长度是无穷大的会导致结果失真。加上-x之后du只统计根文件系统实际占用的空间干净利落。--exclude用于排除你不想统计的目录或文件模式。比如 Docker 的overlay2目录经常占用巨大但你此刻只想看别的目录的分布du -h --max-depth1 /var --excludeoverlay2这个参数在跑定时统计脚本时价值非常大你可以直接把已知的大目录排除掉让脚本速度提升好几个量级。2.3 --apparent-size逻辑大小与磁盘占用的区别前面说过du默认统计的是实际磁盘占用allocated blocks。如果你想知道的是文件的逻辑大小——也就是和ls -l看到一致的数值可以用--apparent-sizedu -sh --apparent-size /var/log对比一下du -sh和du -sh --apparent-size的结果你就能直观看到块对齐造成的空间开销有多大。在排查稀疏文件sparse file的时候这个参数尤其有用。比如一个 10GB 的虚拟磁盘镜像由于内部全是空洞实际占用的磁盘块可能只有 1GB。du显示的是 1GB--apparent-size显示的是 10GB。如果你在做备份或者迁移评估通常要按逻辑大小来估算目标端的需求如果只是看磁盘满了没有那就按默认的物理占用来看。两个口径各有用途分清楚就不会被数字误导。还有一个单位问题值得提。GNU 的du默认块大小在某些发行版上是 1024 字节在设置了POSIXLY_CORRECT环境变量时又是 512 字节。为了避免歧义脚本里建议直接用-B指定块大小或者干脆用-b等价于--apparent-size --block-size1把所有输出统一成字节单位配合后面的数值比较就不会出错。3. 实战案例一条命令揪出占满磁盘的罪魁祸首3.1 从根分区开始逐层下探的标准流程磁盘告警的排查流程其实非常有套路我把它固定成了一套标准动作遇到类似问题十分钟内就能定位。第一步df -h先确认是哪块盘满了、挂载在哪。这一步定方向比如是/满了还是/home满了处理方式完全不同。第二步对目标挂载点从根上跑一层dudu -h --max-depth1 / 2/dev/null | sort -rh | head -10这里两个细节要解释一下。第一个是2/dev/null把没有权限读取的目录报错信息扔掉不然输出会被一堆cannot read directory刷屏真正的结果反而不容易看到。如果你有 sudo 权限更推荐用sudo du ...这样统计到的数据才完整。第二个是sort -rh-r是从大到小排-h是让 GNU sort 能正确识别 K、M、G 这样的单位否则 100M 会被当作小于 10K 的字符串来处理排序完全错乱。第三步看到哪一层最肥就钻进那一层继续dudu -h --max-depth1 /var 2/dev/null | sort -rh | head -10每层只看前 10 名一两轮之后基本就能锁定是哪个目录在作妖。这个逐层下探的思路比直接跑du -ah /要快得多因为后者把整个目录树全部列出来输出巨大不说遍历全盘也特别耗时。3.2 找出指定目录下最大的前N名文件有些场景下你需要找的是文件而不是目录。比如怀疑是某个超大日志文件把盘塞满了这时候可以du -ah /var/log 2/dev/null | sort -rh | head -20-a让du把文件也列出来-h出可读单位sort -rh取大值最后head留前 20 行。这一串组合每个命令只做一件事把输出交给下一个命令加工最后拿到一份干净的结果。如果你想看绝对字节数可以做一个小变化du -ab /var/log 2/dev/null | sort -nr | head -20-b输出的是精确字节数配合sort -nr纯数字排序结果非常可靠适合拿去写进告警脚本或者报表。另外如果你只是想知道某个目录下有没有超过某个尺寸的单个文件用find更合适find /var -type f -size 1G -exec ls -lh {} \;find负责按条件筛选ls负责展示大小。这个命令不计算目录占用只匹配文件速度比重度du快不少。但它的短板是给不出目录维度的聚合大小所以它和du是互补关系。3.3 三个真实的高频场景Docker日志、包缓存与定时备份场景一Docker 目录爆炸。用 Docker 跑了一两年的机器/var/lib/docker经常能吃到几十 GB。最常见的元凶是容器日志因为 Docker 默认把 stdout 日志原样写到/var/lib/docker/containers/容器ID/*-json.log而且不轮转。用du -ah /var/lib/docker/containers 2/dev/null | sort -rh | head -10就能快速定位是哪个容器的日志在疯涨。定位之后要么在docker run时加--log-opt max-size10m --log-opt max-file3做轮转要么在daemon.json里配全局日志轮转。另外/var/lib/docker/overlay2里的镜像层也占了大量空间记得定期docker image prune清理悬空镜像。场景二软件包缓存。apt 的缓存目录/var/cache/apt/archives和 yum 的/var/cache/yum会在长期使用中攒下大量 .deb 或 .rpm 包。很多时候这些包装上之后就再也不会用到了直接占用几个 GB。用du -h --max-depth1 /var/cache看一眼就清楚了然后apt clean或yum clean all就能释放。这种定期缓存膨胀的问题靠du一眼就能诊断算是排查中最轻松的案例。场景三备份文件堆积。不少同学喜欢在服务器上直接丢备份比如 mysqldump 导出的 .sql.gz或者 tar 打包的站点压缩包。备份脚本往往没做保留策略一个月下来积上十几份。用du -h --max-depth1 /backup | sort -rh一看谁大谁小清清楚楚再配合一个简单的保留 N 份的清理脚本这事儿就闭环了。4. 容易踩的坑软链接、隐藏文件、权限报错与性能瓶颈4.1 软链接和硬链接在du眼中是两个完全不同的东西先看软链接。默认情况下du只统计软链接本身占用的空间这个占用小得可怜就是个路径字符串的长度而不会顺着链接去统计目标目录的大小。比如你有/data/current是一个指向/data/release_v2的软链接du -sh /data/current只显示几 KB但/data/release_v2可能是几十 GB。如果想让du跟着软链接走加-L参数du -shL /data/current但要小心-L在遇到循环链接时会无限递归在满是软链接的目录上建议先想清楚再决定要不要加。硬链接则是另一个故事。两个硬链接指向同一个 inode从文件系统的角度看它们本质上是同一个文件。GNUdu在处理这种情况时会做去重一个文件即使有 10 个硬链接也只统计一次磁盘占用。这个设计绝大多数时候是合理的因为空间确实是只占一份。但如果你想统计的是每个目录名义上有多少文件体积比如做配额或迁移评估du的结果可能会让你觉得偏小。这时脑海里要有个概念du的默认值回答的是磁盘真实占用不是逻辑文件总和。4.2 隐藏文件为什么 du 的结果总是比预期少很多人在某个目录下执行du -sh *发现结果和du -sh .对不上差出来的那一块就是隐藏文件。原因很简单shell 的通配符*默认不匹配以点开头的文件所以.git、.cache、.env这些统统没被统计进去。想要包含隐藏文件最稳妥的办法不是手动拼通配符而是直接用目录本身作为统计目标du -sh .或者用--max-depth列出一层目录时隐藏目录一样会被列出来du -h --max-depth1 .至于网上常见的du -sh .[!.]* *这种写法能 work 但很绕而且如果当前目录里有匹配不到的情况shell 还会把原样的通配符字符串传给du把结果搞乱。我的建议是需要看当前目录整体大小就用.需要看子目录分布就用--max-depth1老老实实避开通配符这个坑。4.3 权限报错、冷缓存慢遍历与NFS的噩梦权限问题是du最常见的噪音。普通用户跑du -sh /root或者/var/lib下的部分子目录都会刷出一堆cannot read directory的报错。这些报错不影响其他目录的统计但会让输出没法看。处理办法就两个要么直接sudo提权要么2/dev/null把错误流丢掉。注意如果你在写脚本千万别把2/dev/null去掉否则脚本的输出会被日志污染。性能方面du是单线程串行遍历目录越大越慢。一个几百万文件的目录树冷缓存时可能要跑好几十分钟热缓存刚跑过一次dentry 都在内存里会快很多。所以遇到大目录第一遍du慢是正常的不要以为是卡死了。想快一点可以用--exclude排除掉那些你已知的、不需要统计的大目录用-x排除挂载点别让/proc、/sys拖后腿把统计放到低峰期执行不要和线上业务抢 IO。最慢的是网络文件系统比如 NFS。NFS 上每次stat都可能要一次网络往返目录一大耗时直接起飞。在 NFS 挂载的共享目录上做全量du搞不好要跑一晚上。对这种场景我一般建议统计任务尽量落到 NFS 服务器本地执行或者用快照方式绕过du的逐文件遍历。还有一个容易忽略的点嵌入式设备上的 BusyBoxdu和完整的 GNUdu参数有差异比如某些精简版不支持--max-depth只支持-d动手前先du --help确认一下。5. 把du用出花ncdu、管道组合与巡检脚本5.1 ncdu把du变成可交互的磁盘分析器如果du的逐层下探还是不够直观或者你想在排查的时候顺手把某个目录删掉ncdu是更好的选择。它的全称是 NCurses Disk Usage本质上还是在用du的数据但用一个交互式界面把结果呈现出来。装好之后执行ncdu -x /界面上会显示当前目录下所有子目录和文件的占用按大小排序光标移动、回车进入子目录按d可以直接删除选中项按r刷新。最方便的是顶部会高亮当前目录的百分比自上而下看一圈哪个目录肥一眼就能看出来。ncdu 的安装也简单Debian/Ubuntu 用apt install ncduCentOS/RHEL 用yum install ncdu。在嵌入式环境或者最小化系统里装不上时还有一个终极大法先在能装 ncdu 的机器上du导出结果再把结果文件拿过来分析。ncdu 支持-o导出和-f导入ncdu -xo /tmp/du_export.txt /var ncdu -f /tmp/du_export.txt这个技巧在做远程协助、或者目标机器连屏幕都没有的时候特别好用数据在本地随便翻。5.2 du与sort、awk的组合拳从原始输出到可用报表很多人用du只停留在看一眼其实把它接到管道上能做出很多有用的东西。比如把结果导出成文件留着以后对比趋势du -ah /data 2/dev/null /tmp/data_du_$(date %F).txt或者配合 awk 过滤出超过 1GB 的条目du -ab /var 2/dev/null | awk $1 1073741824 {print $0} | sort -nr这里 1073741824 是 1GB 的字节数。awk 在管道里做数值过滤比 sort 之后再 grep 更靠谱因为 grep 拿字符串匹配数字会出各种哭笑不得的结果。还有一个组合是du套三次实现目录大小变化对比今天跑一次存文件明天再跑一次diff一下就知道哪些目录在疯长。日志类、缓存类的空间增长问题用这种方法两周就能发现规律比每次手动看快多了。5.3 定时巡检与磁盘告警脚本思路最后分享一个我常用的巡检脚本思路。核心就三件事定期跑du输出报表、做简单的阈值判断、把结果交给通知渠道。#!/bin/bash # 每周日凌晨2点执行磁盘空间巡检 THRESHOLD80 OUT/var/log/disk_report_$(date %F).txt { echo $(date %Y-%m-%d %H:%M:%S) 磁盘概览 df -h echo /var 下占用前10的目录 du -h --max-depth2 /var 2/dev/null | sort -rh | head -10 echo /data 下占用前10的目录 du -h --max-depth2 /data 2/dev/null | sort -rh | head -10 } $OUT # 根分区使用率超过阈值则告警 USED$(df -h / | awk NR2 {print $5} | tr -d %) if [ $USED -gt $THRESHOLD ]; then echo 根分区使用率 ${USED}%请检查 $OUT | mail -s [告警] 磁盘空间 opsexample.com fi这个脚本有两个值得借鉴的细节。一是先把du结果落盘再判断避免脚本每次执行都扫描一遍浪费 IO二是告警阈值放在变量里不同机器可以传不同参数不用每个环境改一遍脚本。定时任务就交给 cron0 2 * * 0 /usr/local/bin/disk_check.sh注意在 cron 环境里的 PATH 可能不完整脚本开头最好写上du、df、sort这些命令的绝对路径或者先在脚本里export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin不然跑起来很容易踩命令找不到的坑。最后分享一点我自己的经验。排查磁盘问题时我基本已经形成了肌肉记忆df先定文件系统du -h --max-depth1逐层下探sort -rh排个序最后再根据情况决定要不要上 ncdu。工具不变但思路比工具重要——我见过太多人在第一步就顺手rm掉自认为没用的文件结果删到正在写入的日志或者数据库文件事情越弄越大。磁盘空间再紧张也先花一两分钟看清楚谁占用、为什么占用再动手。数据没了那是真没了。