如果哪天有同事跟我说磁盘满了我第一反应基本都是先跑一条du命令看一眼。du可以理解成 disk usage它的本职就是统计目录或文件实际吃掉多少磁盘块而大家最常要的“查看文件夹大小并按大小排序”本质上就是du配合sort的一套组合拳。这篇文章会把这套组合讲透也会把官方文档里不会写清楚的坑拎出来比如为什么sort -n在这种场景里会排错、为什么--max-depth1并不能加快执行、为什么文件删了空间却没释放。我默认你是在 Linux 环境终端已能敲命令。内容会从参数基础一直讲到脚本化监控新手可以直接照着抄老手可以重点看第 4 节和第 5 节等于帮你把平时容易忽略的边界情况都提前踩一遍。1. 先看懂 du 到底在统计什么1.1 目录大小不是 ls 看到的那个数字很多人刚接触 Linux 时会用ls -l看文件大小用du看目录大小然后发现两边数字对不上。这不是 bug而是两个命令的统计口径不一样。ls -l显示的是文件的逻辑长度也就是“这个文件的内容有多少字节”du默认统计的是文件占用的磁盘块数。文件系统按块分配空间哪怕一个只有 1 字节的文件也可能占满一个 4KB 的块所以du看到的物理占用往往大于ls -l看到的逻辑大小。反过来也有特殊情况。稀疏文件就是一个典型例子逻辑上一看有 1GB实际分配到的块可能只有几 KBdu统计出来就很“瘦”。所以在排查磁盘是否紧张时必须以du、df这类磁盘占用统计为准ls -l只能给你一个文件维度的参考。1.2 du 与 df一个看目录一个看分区du和df很容易被混用但它们的定位完全不同。df看的是文件系统整体的使用情况比如某个分区容量多大、还剩多少、挂载在哪它不会告诉你“这 40G 到底是哪个目录吃的”。du则是一路递归进目录把里面每个文件占用的块加总最终得到某个路径下到底用了多少空间。实际排查流程中我通常先用df -h判断是哪个挂载点满了再用du去定位具体路径。如果挂载点满了但du算出来的总占用远小于df已用空间那就要考虑文件删了但进程还占用、文件系统预留块、快照或元数据开销这些隐藏因素。这个现象在第 4 节会专门展开。1.3 最小可用命令几个高频参数一次讲清du参数不算多但每次都要回忆很烦。我常用的高频参数基本就这几个-s只输出每个参数的总大小不列出内部子项。这个参数配合通配符可以一次看多个目标目录。-h以人可读的单位显示例如K、M、G。-a同时列出文件和目录不只是目录。-c最后给一个总计。-d N或--max-depthN只输出目录树深度小于等于 N 的目录。-x只在同一文件系统内统计不跨越挂载点。--excludePATTERN排除匹配文件或目录。用不了一分钟就能记住。真正容易出问题的是用这些参数组合出“排序”这条链路。2. 查看文件夹大小并按大小排序从组合命令开始2.1 最常用的一行du -sh 目标路径先看最基础的操作du -sh /var/log输出会是这样1.2G /var/log-s让du只输出这个路径的总大小不再递归列出一堆子目录-h把字节数转成G、M、K。如果你想一次比较多个目录就把目标都放在命令后面du -sh /var/log /srv/data /home这是我平时用的最多的形式。一条命令几个重点路径全部出来不需要写 for 循环。不过这里要注意du -sh /srv/*和du -sh /srv的含义完全不同前者统计的是/srv下每一个子项各自的大小后者统计的是整个/srv目录的总大小。2.2 排序为什么必须用 sort -h只列出大小还不够目录一多一眼扫过去很难分出谁是“大户”。这时就要接上sort。常见错误是这么写du -sh /srv/* | sort -n这个写法在大部分情况下都会排错。因为du -h输出的是1.2G、860M这种带单位的值sort -n会把1.2G的数值部分当成 1.2把860M当成 860。按数值排下来860M会排在1.2G后面看着好像没问题实际上 1.2G 明显大于 860M排序已经反了。正确的做法是用sort -h。它能识别K、M、G、T这类人类可读单位按真实大小排序而不是只看裸数字du -sh /srv/* | sort -hr-h表示 human numeric sort-r表示倒序。这样最大目录就会排在最上面。如果你的sort工具不支持-h比如某些比较旧的系统或 BSD 系环境可以退而求其次du -sk /srv/* | sort -rn-k指定以 KB 为单位输出这样sort -rn排序就是可靠的只是显示上没有1.2G这么直观。两种思路都可以关键是理解“带单位的文本不能直接用sort -n排序”这个底层原因。2.3 几个可以直接抄走的模板下面这几个模板是我放在笔记里的碰到磁盘排查直接复制改路径就行。只比较当前目录下的第一层子目录列出最大前 10 个du -sh /srv/* 2/dev/null | sort -hr | head -n 102/dev/null会把权限错误屏蔽掉但副作用是“看不到哪些目录没统计到”。在非 root 环境下跑系统目录时我会先把错误保留确认没问题之后再决定要不要屏蔽。看整个挂载点里哪些一级目录最占空间du -h --max-depth1 /srv 2/dev/null | sort -hr注意--max-depth1的输出里会包含/srv本身的总大小因为起始目录深度是 0。如果你只想要子目录直接用上一个模板更干净。想看更深一层找出大型目录里的小热点du -h --max-depth2 /srv 2/dev/null | sort -hr | head -n 30如果文件名和目录名带空格用通配符*时也要小心。更稳的方式是用find把参数安全地传给dufind /srv -maxdepth 1 -mindepth 1 -print0 | xargs -0 du -sh | sort -hr这样目录名里的空格不会被切碎隐藏文件也能包含进来。这个方法我一般在/home下面用因为用户目录太容易出现各种奇怪文件名了。3. 真实场景把 du 用出生产力3.1 找出第一层子目录里谁在“吃”磁盘最常见的需求是已知/srv满了想知道哪个业务模块占最多。这时代入这个命令du -sh /srv/* | sort -hr | head -n 20结果通常长得像这样45G /srv/data 3.2G /srv/backup 860M /srv/logs 12M /srv/app只看一眼就能确定data是主要问题。如果data下面还细分很多项目下一步就继续下钻du -sh /srv/data/* | sort -hr | head -n 20这就是最简单的“逐层下钻法”。每次只列出当前层最大的几个项找到可疑目标后再往下一层看。逻辑很直白但很有效。3.2 排查整个分区一路下钻找出真实大户不过有些环境里问题不一定在第一层就能暴露。比如/srv/data下面每个一级目录都不算大但某个二级目录大到离谱那种情况下第一层排序只能看到一级目录合计值看不到内部结构。我会分两步走。先看一级目录手动锁定可疑项再对可疑项用--max-depth2展开du -h --max-depth2 /srv/data | sort -hr | head -n 30输出里会同时出现/srv/data/project/logs这种路径。因为du会把目录树里每个深度小于等于 2 的目录都输出一次所以要习惯结果里有些目录是父子关系数值大的通常是汇总后的父目录真正需要关注的是排在前面但和父目录数值接近的那几个叶子路径。判断方法很简单如果一个子目录的值基本等于它上级目录的值那问题就集中在这个子目录里如果上级目录的值远大于所有子目录之和那就要注意有没有隐藏在目录里的普通大文件。3.3 混合查看大文件和大目录时怎么区分很多人也会顺手用这个命令du -ah /srv 2/dev/null | sort -hr | head -n 20-a会把普通文件也输出出来。但注意du -a的输出里目录项依然会带上它下面所有子孙内容的总和。所以最终结果中大目录会占掉大部分前排位置真正的“大文件”反而不容易露头。如果你关注的是“哪个普通文件最大”用du -a不如用find来得干净find /srv -type f -printf %s\t%p\n 2/dev/null | sort -rn | head -n 20这是按字节数排序输出不带单位清晰度很高。遇到路径里有空格制表符分隔也能让 sort 稳定工作。如果系统find不支持-printf可以用stat配合循环但日常排查里-printf已经足够。du -a更适合的用途是我想知道“这个目录下所有项里有没有某个单个文件也特别大”这时候前半部分目录忽略掉重点看后半部分的小单位输出。3.4 排除无意义的目录只统计需要的部分在项目目录里排查磁盘通常第一眼就会被node_modules、.git、build、cache这些“假大户”干扰。它们确实占空间但往往不是这次要处理的业务数据。与其在结果里反复辨认不如一开始就排除du -sh --excludenode_modules --exclude.git --excludecache /srv/project--exclude可以和-s组合也可以配合通配符。du -sh /srv/project/* --exclude*.log | sort -hr这样*.log文件不会进入统计。注意这里的过滤规则和 shell 通配符不是一回事它由du自己解析不用先展开成文件列表。排除了之后再看结果才不会被日志文件、临时文件带偏方向。我实际用的时候会先跑一次不带排除的完整统计再跑一次带排除的统计。两个数字相减就能估算出“哪些类型文件吃得最狠”这对后续清理策略很有帮助。3.5 输出成报告方便发出去给别人做决策很多时候我查完磁盘不会只给自己看而是要告诉项目负责人“这个目录再不清就爆了”。这时候纯命令输出不够友好。我会生成一个带时间戳的文本报告{ echo disk report $(date %F %T) echo ----- /srv top dirs ----- du -sh /srv/* 2/dev/null | sort -hr | head -n 15 echo ----- /var/log top dirs ----- du -sh /var/log/* 2/dev/null | sort -hr | head -n 15 } /tmp/disk_report.txt也可以顺手压缩一份cat /tmp/disk_report.txt | mail -s disk report adminexample.com如果没有邮件服务写进日志文件再配合监控告警也是常用做法。关键点在于执行报告的人不一定有权限看到原始数据所以在报告里保留路径和单位比只给一个总数有用得多。4. 常见问题与排查技巧实录4.1 du 执行特别慢怎么提速du之所以慢是因为它需要递归读取目录下的所有条目还要逐个stat获取元数据。文件数量一上百万执行时间自然以分钟甚至小时计。很多人误以为--max-depth1只扫一层会很快其实不是这样为了统计第一层目录的总大小du必须把每个子目录内部的所有文件都遍历完。提速思路主要有三个方向。第一是用--exclude去掉无意义的缓存和临时目录让遍历范围缩小。第二是使用-x遇到网络盘或跨盘挂载就不往下走可以避免把流量耗在远程文件系统上。第三是放到后台执行降低对前台操作的干扰ionice -c3 nice -n 19 du -sh /srv/* 2/dev/null | sort -hr /tmp/du_result.txt ionice -c3让磁盘 IO 以 idle 级别运行nice -n 19让 CPU 优先级降到最低。这样在线服务器上跑大目录统计时行为会温和很多。虽然不会让du本身变快但至少不会把业务读写拖垮。4.2 统计出来的大小和实际对不上新手最容易困惑的一类问题是du -sh /srv显示 20Gdf -h显示分区已用 40G差的十几G去哪了常见原因有这么几个现象可能原因怎么确认du df有文件被删除但进程仍持有lsof L1或lsof | grep deleteddu df文件系统预留块tune2fs -l /dev/对应分区 | grep Reserveddu df快照、镜像、元数据检查备份工具和文件系统快照du df跨挂载点重复统计用du -x重新统计还有一种情况是硬链接。多个硬链接指向同一个 inode如果分别统计大小会被算多。GNU 的du默认会去重一部分硬链接但如果你用了-l强制统计数字也会明显变大。真要严格核算需要把“目录逻辑占用”和“文件系统物理使用”分开看待两者本来就不是完全相等的概念。4.3 文件删了空间没释放是怎么回事这个场景我遇到太多了。rm删掉了一个 10G 的日志文件ls也看不到了但df -h的已用空间纹丝不动。这是典型的“有进程还持有该文件句柄”。Linux 下文件被删但进程没关闭句柄时空间不会立刻释放直到进程退出或句柄关闭。排查命令是lsof L1L1的含义是列出 link count 小于 1 的文件也就是已经 unlink 但仍打开的文件。找到对应进程后和业务方确认再重启进程或重载配置空间才会真正释放。需要注意的是手动删文件之前最好先确认这个文件是不是被程序写进去的。很多日志滚动不会自动删旧文件但程序会继续持有句柄如果你直接删掉了正在写入的日志路径虽然空间看起来释放了日志滚动策略可能反而会出问题。4.4 权限不够导致漏统计以普通用户跑du -sh /root会提示 permission denied但du不会因此退出它会继续处理能访问的部分最后给一个不完整结果。如果后面还接了2/dev/null那你看到的数字就是“静默缩小”的。排查关键系统目录时我建议先用 root 或 sudo 执行sudo du -sh --max-depth1 /var | sort -hr如果实在无法拿到权限至少要保留错误输出比如du -sh /var/* 21 | grep -E denied|du:这样你能知道哪些目录没被统计进去。否则一个报表发出去别人看到数字偏小反而会误导决策。4.5 符号链接和跨盘挂载带来的重复统计du默认不会追进遍历过程中遇到的符号链接目录所以常规统计并不容易因为符号链接而重复计算。真正容易翻车的是跨盘挂载。比如/srv/data下面挂了一个独立数据盘你直接du -sh /srv它会把这块盘的内容也算进去而df -h看的是另一个分区两个结果自然对不上。解决办法就是加-xdu -xsh /srv-x等于告诉du只在当前文件系统内统计不要跨挂载点。我在多云、多盘环境下几乎必加这个参数避免统计结果被相邻挂载点污染。5. 不止手动排查把 du 接进日常运维5.1 定时抓取 Top N 目录并留存历史如果你的服务器隔三差五被磁盘告警惊扰只靠人肉敲命令不行最好让机器每天自动留一份报告。写一个简单脚本#!/usr/bin/env bash REPORT_DIR/var/log/disk_report mkdir -p $REPORT_DIR DATE$(date %F) for target in /srv /var /home; do { echo $target $DATE du -h --max-depth1 $target 2/dev/null | sort -hr | head -n 10 } ${REPORT_DIR}/${DATE}.txt done配合 cron 每天执行一次就能看到目录大小随时间的变化趋势。磁盘增长是渐变还是突增翻一下历史报告就知道避免了“等告警出来了才到处抓瞎”的被动局面。5.2 超过阈值自动通知更进一步的方案是写一个阈值判断脚本。比如监控/srv/data是否超过 10G#!/usr/bin/env bash TARGET/srv/data LIMIT_M10240 SIZE_M$(du -ms $TARGET 2/dev/null | awk {print $1}) if [ $SIZE_M -gt $LIMIT_M ]; then echo [$TARGET] usage ${SIZE_M}M over limit ${LIMIT_M}M | mail -s disk alarm adminexample.com fi这里用du -ms输出以 MB 为单位的数值awk {print $1}只取数字然后再和阈值比较。之所以不用du -h做判断是因为带单位的字符串比较很容易踩坑直接用纯数字最安全。实际生产里邮件不一定可靠很多人会改成调用企业微信或钉钉 webhook。形式可以根据你的环境来核心思路是先让du给出一个稳定、可比较的数值再交给告警逻辑处理。5.3 清理前的几个保守操作磁盘告警一旦触发很多人上来就想rm -rf。我建议动手前先做好三步确认第一先看有没有删除但未释放的文件也就是 4.3 节说的lsof L1。如果有优先处理持有句柄的进程而不是再删其他文件。第二大目录先排序再按“目录大小 / 文件 mtime”交叉判断。比如du -sh /srv/data/* | sort -hr | head -n 10 find /srv/data -type f -mtime 180 -size 100M -print 2/dev/null | head -n 20旧的大文件通常是最容易被清理的对象但清理前要确认这些文件是不是还有审计、合规或回滚需求。第三删除前尽量先移动而不是直接删。比如把候选文件移到隔离目录观察几天业务是否正常mkdir -p /srv/.trash mv /srv/data/old_project /srv/.trash/确认没问题后再从隔离目录里真正清理。这个习惯帮我避过很多次“删完才发现还要用”的麻烦。现在遇到磁盘告警我的固定动作依旧是先du看一眼再sort -hr排个序。这套组合本身不复杂难的是在真实环境里保持判断力——知道哪个数字可信、哪个目录可以排除、哪类空间差额需要进一步查。把这些经验沉淀成一条条命令和核查流程你也会发现磁盘排查并不是一件靠猜的事。