拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux磁盘空间管理实战:用du命令精准定位空间占用

Linux磁盘空间管理实战:用du命令精准定位空间占用

在Linux服务器上待久了,一定会遇到磁盘被塞满的尴尬。登录不上、服务报错、日志写不进去,一查df -h,好家伙,/分区直接100%。这时候你需要的不是df,而是du——它是Linux下做磁盘空间管理最趁手的工具,能精确告诉你“每个目录到底吃了多少字节”,帮你在几分钟内锁定罪魁祸首。这篇文章不讲花架子,直接从实战出发,把du的常用参数、典型场景、踩坑记录都摊开讲,适合刚入门的新手,也适合被磁盘问题折磨过多次的运维老手。

1. 磁盘空间管理为什么要选du

1.1 du 与 df 的分工

很多人一开始容易把df和du搞混,其实他俩是互补关系。df(disk free)站在文件系统层面看整体使用率,它告诉你“这块盘还剩多少、用了多少”,但没法告诉你“具体是哪个目录把空间吃掉了”。而du(disk usage)恰好补上这个缺口,它从目录树底层往上逐层统计每个文件、每个目录占用的实际块大小,是定位大文件和大目录的利器。

举个生活化例子:df就像小区物业的水表,只告诉你这栋楼这个月总用水量;du则像每家每户的水表,能查出到底是301室还是502室在疯狂用水。所以实际操作中,我惯用的顺序是:先用df确认哪个分区爆了,再用du深入该分区的挂载点,一层层往下揪出大目录。两者配合,磁盘空间管理才算闭环。

1.2 du 的核心思路:统计文件占用的块

理解du之前,先要知道文件在磁盘上的存储方式。Linux文件系统(如ext4、xfs)按块(block)分配空间,一个块通常是4KB(4096字节)。哪怕一个文件只有一个字节,它也会占用一个完整的数据块,加上文件系统自身记录的元数据(inode、目录项等),实际占用空间往往大于文件逻辑大小。du统计的就是这种“实际占用的物理块大小”,也就是你df里看到空间减少的原因。

这也解释了为什么用ls -l看到的文件大小和du输出的占用空间经常不一致——前者是逻辑长度,后者是与空间占用直接相关的块数。理解这一层,后面遇到的很多“诡异现象”就能看得通了。du的默认输出以512字节或1K块为单位(不同发行版略有差异),稍后会讲如何用-h转成人类可读格式,避免每次都要心算。

2. du 命令的基础用法与参数拆解

2.1 最简用法与输出解读

不加任何参数直接跑du,它会从当前目录开始,递归统计所有子目录的占用,并逐行输出。你看到的输出长这样:

$ du 4 ./.cache 16 ./config 840 .

每一行最前面是目录实际占用的块数,默认单位是1024字节(也就是1K)。最后一行的.表示当前目录总占用。这个默认输出在真实环境里很乱——目录一多,刷屏刷到你怀疑人生。所以日常我用得最多的组合是du -sh,小写h表示human-readable(人类可读),小写s表示summarize(汇总),合起来就是“只显示当前目录总大小,不要逐个子目录刷屏”。这个命令几乎是我在每台服务器上都刻进肌肉记忆的第一条。

$ du -sh /var/log 1.2G /var/log

2.2 高频参数详解

光会-sh肯定不够,排查问题时还得会用下面这几个参数:

参数作用典型场景
-h以K、M、G等易读单位显示du -sh *
-s只汇总显示总计,不列出子目录du -sh /opt
-a同时统计文件,而不仅是目录找出特定大小的文件
-d N递归深度限制为N层du -h --max-depth=1
--exclude排除匹配的文件或目录统计时跳过备份目录
--time显示目录内文件最近修改时间排查旧日志

其中-d(或等价的--max-depth)尤其好用。比如你想看/根目录下第一层目录各占多少空间,执行du -h -d 1 /,输出会精简贴近需求:每个一级目录一行,浅显直观。如果直接跑du -h /,那会把每个子目录的孙子目录全打印出来,输出量爆炸不说,对排查毫无帮助。

还有一个容易被忽略的参数是-x(--one-file-system)。它告诉du不要跨文件系统统计。什么意思?如果你的/home是独立分区挂载的,不带-x时,du -sh /会把/home分区的空间也一并算进去(因为它是根目录下的子目录),导致总量虚高。加了-x,du就只统计根分区自己的文件,不会再跑到别的挂载点上数一遍。这点在有多块磁盘或做NFS挂载的服务器上特别重要,稍后坑点章节再详细展开。

2.3 可读性与单位换算

du默认按1024字节的块数输出,不带单位,数字大了很难一眼看出大小。加了-h后,输出会自动变为4.0K、12M、2.3G这种格式。注意这里的单位进制:Linux习惯使用1024进制,所以1G其实是1024M而非1000M,和硬盘厂商的1000进制不是一回事。这也是为什么你用df -h看到的容量和硬盘标称容量对不上号,别慌,单位进制不同而已。

除了-h,还有--si参数可以改成1000进制输出。99%的日常使用不需要关心这个差别,但如果你写脚本比较数值,就一定要统一单位。我的建议是:做人肉排查时用-h,写自动化脚本时一律改用-k(强制以KB为单位)或-m(MB),这样结果稳定,不会因为可读格式解析出问题。

3. 实操:三个真实场景带你玩转du

3.1 场景一:排查磁盘被谁占满

假设你在凌晨被运维告警吵醒,/dev/vda1使用率98%。正常操作是这样:

df -h du -x -h -d 1 / 2>/dev/null | sort -rh | head -20

第一行df -h确认是根分区还是其他分区满了。第二行是关键:-x防止跨文件系统重复统计,-d 1只看根目录下一级子目录,sort -rh按人类可读大小倒序排序(-r倒序、-h按人类可读数字比较,注意这里的r和h顺序不能错),head -20只取最大的20个。这样几分钟内就能定位到/var、/home还是/opt在膨胀。

定位到具体目录后,继续下钻。比如发现是/var问题,就执行:

du -h -d 1 /var 2>/dev/null | sort -rh | head

逐层往里走,最终会找到一个具体目录,比如/var/log/nginx,然后再配合ls -lht或find找具体文件。这套“层层下钻”的思路,本质是不断缩小排查范围,我接手过的带病服务器几乎全是用这一套流程救活的,效率远高于在/底下盲目跑du -h然后人肉翻屏。

3.2 场景二:找出目录下最大的文件

有时候不是目录多,而是单个文件巨大。比如某个应用把日志写进了/tmp/app.log,一下子涨到20G。要找大文件,可以在目标目录下执行:

du -ah /data 2>/dev/null | sort -rh | head -20

-a参数会让du把所有文件也列出来,而不只是目录。配合排序和head,最大的一批文件直接浮出水面。注意du -a的输出量很大,建议指定具体目录,别一上来就扫/,否则跑个半天不说,内容还多到找不到重点。

如果你只想看大于某个阈值的文件,du自身没有--size过滤功能,得配合find实现:

find /data -type f -size +1G -exec du -h {} \; | sort -rh

这条命令先让find筛出大于1G的普通文件,再对每个文件执行du -h。我实测过,对于几十万小文件的目录,find -size +1G -exec du -h {}的组合比纯du -a快得多,因为du -a需要把每个文件的尺寸都算出来,而find可以利用文件系统的元数据做粗筛。

3.3 场景三:脚本化统计并输出排序

人肉排查适合偶发问题,但如果公司服务器多,我建议把统计流程脚本化。下面这段是我常用的健壮版脚本,适合crontab定时执行并把结果发到日志:

#!/bin/bash # 磁盘空间统计脚本:scan_disk_usage.sh TARGET="/var" OUTPUT="/tmp/disk_report_$(date +%F).txt" { echo "=== Disk usage report for $TARGET ===" echo "Generated at: $(date '+%Y-%m-%d %H:%M:%S')" du -x -h -d 1 "$TARGET" 2>/dev/null | sort -rh | head -20 echo "=== Top 10 largest files ===" find "$TARGET" -type f -size +500M -exec du -h {} \; 2>/dev/null | sort -rh | head -10 } > "$OUTPUT" echo "Report saved to $OUTPUT"

脚本核心还是du,但我额外做了三件事:第一,所有du输出后面加了2>/dev/null,把权限拒绝等错误信息丢掉,避免脏数据;第二,输出重定向到带日期的文件,方便留痕对比;第三,把目录统计和大文件统计分开做,避免互相干扰。把这个脚本丢进crontab每天凌晨跑一次,磁盘告警很多都能提前发现而不是等爆了才处理。

4. 常见坑与排查经验

4.1 权限不足导致的统计偏差

在没有权限的目录上跑du,它默认会给出一条类似du: cannot read directory '/root': Permission denied的警告,并且这个目录下面所有内容都不会被统计进去。这会造成统计结果偏低。比如某个进程以root身份在跑,产生的文件放在/root下,而你用普通用户执行du -sh /,看到的结果可能“意外地小”。别高兴太早,这只是权限遮住了真相。

我的处理方式是有两种:一是明确知道需要统计完整时,尽量用root账号或者sudo执行du;二是将错误输出重定向到单独文件,观察哪些目录没被统计。比如:

sudo du -x -h -d 1 / 2>/tmp/du_errors.log | sort -rh | head -20

排查完顺手cat /tmp/du_errors.log,看看有没有重要目录被挡在外面。这比单纯用2>/dev/null吞掉报错要靠谱得多,因为吞掉报错后你根本不知道哪些数据缺失了。

4.2 硬链接与重复计数的迷惑

硬链接是Linux文件系统里一个容易让人困惑的机制。两个不同目录下的文件名,如果指向同一个inode,它们其实是同一个文件。但du默认对每个目录分别统计,导致同一个文件在两个路径里各被算一次,最终总占用虚高。

典型场景是backup类工具(如rsync带--link-dest)会创建大量硬链接文件。你用du -sh分别看两个备份目录,每个都显示10G,总共20G,实际物理占用可能只有10G。解决方案是加-l参数吗?不对,-l是让du把硬链接按多个文件计数(默认它会自动识别硬链接并只计一次,但目录树不同情况不同)。

实际排查中,如果不确定有没有硬链接干扰,可以先用stat查看文件inode编号,再用find / -inum 12345找出同一inode的所有路径。如果确认是硬链接导致虚高,别担心,清理其中一个路径不影响另一个。关键是心里有数,别误判“磁盘被重复占用了”。

4.3 稀疏文件与空洞文件

有些程序会创建稀疏文件(sparse file),典型代表是数据库临时文件、虚拟机磁盘镜像。稀疏文件的特点是逻辑大小很大,但实际分配的物理块很少,因为中间全是空洞(全是0)。比如一个容器镜像文件,ls -lh显示100G,du -h可能只有2G,就是这个原理。

ls和du在这里差距巨大,不代表系统出错。如果你想看宽松占用,记住du才是物理真相。反过来,如果你用cp去复制一个稀疏文件,标准cp会把它还原成实际大小,瞬间撑爆目标盘。这时要用cp --sparse=always保持稀疏属性。这个坑我踩过不止一次,复制大文件前一定先用du看一眼真实占用。

4.4 du 与 ls 显示大小不一致

接上面话题,du与ls -l对同一个文件显示大小不同,原因主要有三个:一是块大小对齐(前面说过,小文件按块分配,ls是逻辑大小,du是实际占用块);二是稀疏文件;三是文件系统压缩或快照功能影响(如btrfs、ZFS)。

遇到用户拿着ls -l1.5G和du12K的对比来问“是不是bug”时,别解释半天,直接告诉他这个文件可能是稀疏文件或空文件。验证办法很简单:

ls -lh file du -h file stat file | grep -E 'Size|Blocks'

stat里的Blocks字段才是真正分配的数据块数量,乘以块大小就是du的结果。掌握这个验证链,以后再遇到大小不一致的疑案都能轻松释疑。

4.5 挂载点与跨文件系统的陷阱

服务器磁盘规划复杂时,根目录下会挂着很多其他分区。比如/data是独立的一块2T数据盘,/backup是NFS远程挂载。此时你在根目录执行du -sh /,会把/data和/backup的数据也算进来,看着像一个分区占用超大,实际却是好几个设备的效果。

多数情况下统计单分区使用量,都建议加-x参数。这里有个补充经验:du -x只统计当前文件系统,但并不会自动跳过“挂载点本身入口”的目录元数据,所以根目录下的挂载点仍然会显示一个很小的数值,这个正常。如果需要列出哪些目录是独立挂载点,用mount或findmnt查看,比纠结du的输出更有用。

还有一点,du在NFS挂载目录上跑会非常慢,因为要遍历远程文件系统,每一次stat都是一次网络往返。遇到NFS目录,我建议直接用远程端的工具统计,或者等业务低峰期再跑,别在白天高峰对着NFS执行du -sh /mnt/nfs,卡到怀疑人生。

5. 进阶技巧与自动化

5.1 结合 find 清理历史日志

大日志文件往往是磁盘爆满的“元凶”。用du找到大目录后,删除前建议先确认文件是否可删。我常用的安全删除流程是:

# 找到 /var/log 下超过 200M 的日志文件 find /var/log -type f -size +200M -exec ls -lh {} \; # 确认后,先清空而不是直接 rm,给进程留个活口 : > /var/log/app.log

直接rm掉一个正在被进程写入的日志文件不会立即释放磁盘空间,因为进程还握着它的文件句柄,空间要等进程关闭或重启后才释放。此时磁盘还是满的,你会陷入“明明删了文件磁盘却没变”的困惑。正确姿势是先用du -sh *定位,再利用lsof | grep deleted找出被删但未释放的句柄,最后用> 文件清空内容或重启对应服务。这个组合拳帮我解决过无数次“磁盘满但不知道删什么”的奇怪Case。

5.2 定时任务自动发送报告

前面写了统计脚本,配合crontab可以做成告警提示。下面是一个简单范例:

# 每天凌晨2点执行磁盘统计,输出保存 0 2 * * * /opt/scripts/scan_disk_usage.sh > /dev/null 2>&1 # 每周一9点检查/分区使用率,超过85%发邮件提醒 0 9 * * 1 awk '$NF=="/"{if($5+0>=85) system("echo Disk usage high: "$5" | mail -s \"Disk Alert\" admin@example.com")}' <(df -h)

du是主力工具,df来辅助判断阈值。实际生产中,我不会只依赖一个工具,而是把du和df的结果合并成一个报表,既看整体水位又看明细分布。脚本输出格式建议使用CSV,方便后续导入监控系统:

du -x -d 1 / 2>/dev/null | awk '{print $2","$1}' > /tmp/disk_usage.csv

这样后续无论是画图还是入库,都有结构化数据可用。

5.3 用 du 判断 inode 与目录深度

磁盘满不全是块空间问题,还有inode耗尽的情况。df -i可以看到inode使用率。当磁盘空间还有但inode满时,文件无法创建,症状和磁盘满类似。此时du并不能直接看inode数量,但它能帮你找出哪个目录下小文件最多。

比较笨但有效的办法是在目标目录下递归数文件数量:

find /data -type f | wc -l

配合du的目录大小分布,基本能判断出是小文件泛滥还是大文件撑爆。我遇到过一种极端情形:某个缓存目录里生成了上千万个4K大小的烤面包屑文件,du -sh看目录可能只有几个G,但find | wc -l一数,几千万,inode直接被吃干抹净。这时候用du逐个目录扫,找到那个增长最快的目录,再结合业务日志清理旧缓存,才真正解决问题。du在这类场景的价值是“帮我把注意力集中到异常成长的目录”,至于怎么清理,那是另外的工具和策略。

还有一个经验是配合--max-depth控制递归深度,避免误入超级深层的目录结构。比如du -h -d 0等价于du -sh,只统计当前目录本身;du -h -d 2统计两层以内的目录。深度太大时,某些应用生成的目录树有几十层嵌套,全量du输出会非常恐怖,限制深度再配合sort,能快速找到“树中最粗的那根枝干”。

6. 关于个人经验的一个小补充

最后分享一个我自己的土办法:在排查磁盘问题时,我会给du加一个时间戳对比。比如今天记录一次du -sh /var,明天再记录一次,看差异。单纯看绝对值很难判断是正常增长还是异常膨胀,但看增量就一下子暴露问题。做法很简单:

# 记录基线 du -sh /var > /tmp/disk_baseline.txt # 隔天再跑 du -sh /var >> /tmp/disk_baseline.txt # 对比 cat /tmp/disk_baseline.txt

数据一对比,那种一夜涨几个G的异常马上就现出原形。这个方法和我前面说的脚本化是一脉相承的,都是“让数据替你说话”。du这个命令本身不算复杂,难的是怎么顺手、怎么调节参数配合其他命令。希望这些实战细节对你有用,下次再遇到磁盘告警,心里能更有底。

返回列表