尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Linux文件大小真相:ls与du差异原理及磁盘排查实战

发布时间:2026/9/29 9:35:24

资讯中心
01
ARTICLE

Linux文件大小真相:ls与du差异原理及磁盘排查实战

Linux文件大小真相:ls与du差异原理及磁盘排查实战
1. 为什么“看大小”这件事在Linux里从来不是一句ls就能解决的刚接触Linux时我跟大多数人一样以为ls -lh就是查看文件大小的终极答案——直到某天在一台生产服务器上发现一个明明只有几KB的配置文件ls -lh显示是4.0K而用du -sh一查居然占了256MB。当时手心全是汗赶紧杀掉进程、清空日志、重启服务最后才发现是那个配置文件被某个程序以追加模式疯狂写入但文件句柄没释放ls看到的是“逻辑大小”du看到的才是“磁盘真实占用”。这事儿让我彻底明白在Linux里“文件占多大空间”根本不是单一维度的问题它至少要拆成三个层面来看——元数据记录的大小、磁盘实际分配的块大小、以及文件系统底层的稀疏/硬链接/挂载点穿透等隐藏变量。你搜“linux命令 查看文件大小”前五条结果几乎全是ls -lh和du -sh的并列罗列但没人告诉你什么时候该信ls什么时候必须用du什么时候还得加-x或--apparent-size甚至什么时候连du都会撒谎。这不是命令记不住而是没搞清Linux文件系统的底层契约——inode只存逻辑长度block才管物理占地硬链接共享inode但不共享block挂载点会穿透统计稀疏文件更是表面轻盈、内里臃肿。今天这篇我就用真实运维场景里的七次踩坑经历把“看大小”这件事从命令表层一直挖到ext4文件系统源码级逻辑不讲概念只说你明天就能用上的判断链路和实操口诀。提示本文所有命令均基于主流发行版CentOS 7/Ubuntu 18.04的默认coreutils版本du 8.30, ls 8.30不依赖第三方工具。所有测试均在ext4/xfs文件系统下验证btrfs/zfs等特殊文件系统行为差异会在对应章节单独标注。2. ls -lh你看到的只是文件系统的“户口本登记页”2.1 ls命令的本质——读取inode而非扫描磁盘块ls命令真正读取的是文件对应的inode结构体中的st_size字段。这个字段在Linux内核中定义为struct stat { ... off_t st_size; /* total size, in bytes */ ... };而off_t本质上是long long类型它存储的是文件的逻辑字节长度——也就是你用echo hello a.txt后a.txt的st_size就是6。但关键在于这个数字完全不反映磁盘上实际占用了多少block。为什么因为文件系统为了性能会预分配block或者允许稀疏文件存在“空洞”。举个最典型的例子创建一个1GB的稀疏文件# 创建一个逻辑大小1GB但实际只占1个block的文件 dd if/dev/zero ofsparse.img bs1G seek1 count0 # 查看ls结果 ls -lh sparse.img # 输出-rw-r--r-- 1 root root 1.0G xxx xx xx:xx sparse.img # 查看du结果 du -sh sparse.img # 输出4.0K sparse.img这里ls报1.0G是因为seek1让文件指针跳到1GB位置再写实际没写任何数据st_size就被设为1073741824字节而du只统计真正分配了数据的block所以只有4KB。如果你误信ls去清理“大文件”就会漏掉真正的磁盘杀手。2.2 -lh参数背后的单位换算陷阱ls -lh的-hhuman-readable看似友好实则暗藏精度陷阱。它采用二进制单位换算KiB/MiB/GiB但显示时做了四舍五入# 创建一个刚好1024*10241字节的文件1MiB1B dd if/dev/zero oftest.bin bs1 count$((1024*10241)) ls -lh test.bin # 输出-rw-r--r-- 1 root root 1.1M xxx xx xx:xx test.bin du -h test.bin # 输出1.1M test.bin注意1.1M在这里是1126400字节1.1 × 1024 × 1024但实际文件是1048577字节。ls把1048577 / 1024 / 1024 1.000000953...四舍五入成了1.1M——这是ls的显示策略不是计算错误。但当你需要精确比对比如校验备份完整性就必须用ls -l显示原始字节数ls -l test.bin # 输出-rw-r--r-- 1 root root 1048577 xxx xx xx:xx test.bin注意du -h同样用二进制单位但它的四舍五入规则更保守通常保留一位小数且阈值不同。实测中ls -lh和du -h对同一文件的显示值可能差0.1M这不是bug是设计使然——它们服务的目标不同ls服务于人眼快速识别du服务于磁盘容量规划。2.3 ls无法穿透的三大盲区硬链接、挂载点、删除未释放文件ls的局限性在复杂场景下暴露无遗。我曾在线上排查一次磁盘爆满事件ls -lR /var/log显示所有日志加起来不到5GB但df -h显示/var分区使用率98%。最终发现罪魁祸首是一个被rm删除但进程仍在写入的/var/log/app.log——ls已经看不到它了但du还能通过/proc/*/fd/找到它# 查找已删除但仍被占用的文件 lsof L1 | grep deleted # 或直接扫描/proc下的fd find /proc/*/fd -ls 2/dev/null | grep deleted更隐蔽的是硬链接问题。假设你有10个指向同一inode的硬链接每个都用ls -lh看是100MB你会误以为占了1GB其实du只算一次# 创建硬链接 dd if/dev/zero ofbigfile bs1M count100 ln bigfile link1 ln bigfile link2 ls -lh bigfile link1 link2 # 全部显示100M du -sh bigfile link1 link2 # 输出100M total不是300M还有挂载点穿透ls -lR /mnt会递归列出所有子目录但若/mnt/usb是另一个挂载的U盘ls会把它当普通目录统计而du默认会跨挂载点除非加-x# /mnt/usb是独立挂载的设备 ls -lh /mnt/usb # 显示U盘内容大小 du -sh /mnt # 默认包含/mnt/usb的大小可能不是你想要的 du -shx /mnt # 加-x后忽略其他挂载点只算/mnt本身这些都不是ls的缺陷而是它的设计定位决定的——它是个元数据快照工具不是磁盘空间审计工具。3. du -sh真正丈量磁盘的“卷尺”但用错参数等于蒙眼测量3.1 du的核心逻辑遍历目录树累加每个文件的disk usagedudisk usage的源码逻辑非常直白从指定路径开始递归调用stat()获取每个文件的st_blocks字段然后乘以st_blksize文件系统块大小最后求和。st_blocks单位是512字节块POSIX标准所以du的计算公式是实际占用字节数 st_blocks × 512这就是为什么du的结果永远≥ls的结果——st_blocks统计的是分配的block数哪怕block里只写了1字节。验证一下# 创建一个只写1字节的文件 echo a tiny.txt ls -l tiny.txt # 显示 size2含换行符 du -b tiny.txt # 输出4096 tiny.txt因为ext4默认block size4KBdu -b强制按字节显示但底层仍是st_blocks×512。-sh中的-ssummarize让du只输出总计-h同ls做二进制单位转换。3.2 -sh组合的致命误区-s不等于“只看当前目录”而是“只汇总不展开”新手常以为du -sh /home只统计/home目录本身的大小其实不然。du的-s参数含义是suppress subdirectories抑制子目录展开但它依然会递归统计所有子目录的内容只是不逐行打印每个子目录的大小只输出总和# 目录结构 /home/ ├── user1/ │ └── docs/ │ └── report.pdf (10MB) └── user2/ └── pics/ └── photo.jpg (5MB) du -sh /home # 输出15M /home正确总和 du -sh /home/* # 输出10M /home/user1 5M /home/user2分别统计 du -sh /home/user1 # 输出10M /home/user1递归统计user1下所有真正想“只看当前目录下一级内容的大小”必须用--max-depth1du -sh --max-depth1 /home # 输出 # 10M /home/user1 # 5M /home/user2 # 15M /home我见过太多人用du -sh /var/log后发现结果异常大就以为是/var/log目录本身臃肿其实是/var/log/journal这个子目录占了90%而-s掩盖了细节。du -sh适合快速获知总量du -sh --max-depth1才是定位大户的黄金组合。3.3 必须掌握的四个du变体参数-x, --apparent-size, -c, -a-x跨文件系统时的“防火墙”当目录树包含多个挂载点如/home是独立分区/home/user/backup又挂载了NASdu默认会穿透所有挂载点统计。这往往不是你想要的——比如你想知道本地磁盘用了多少却把远程NAS的10TB也算进来了。-xone file system参数强制du只统计当前文件系统# /home和/home/user/nas是不同挂载点 mount | grep home # /dev/sda2 on /home type ext4 (rw,relatime) # //nas/share on /home/user/nas type cifs (rw,relatime) du -sh /home # 包含nas的大小危险 du -shx /home # 只算/dev/sda2上的数据安全--apparent-size让du“假装看不见”稀疏文件的空洞前面稀疏文件的例子中du显示4KB而ls显示1GB是因为du统计的是实际block。但有时你需要知道“如果这个文件是稠密的它该有多大”——这就是--apparent-size的作用它改用st_size而非st_blocks计算dd if/dev/zero ofsparse.img bs1G seek1 count0 du -sh sparse.img # 4.0K du -sh --apparent-size sparse.img # 1.0G和ls一致这个参数在备份场景极有用rsync --sparse创建的稀疏文件用du --apparent-size能预估还原后的真实大小。-c批量统计时的“自动求和”当你需要对比多个路径的大小-ctotal会自动在最后加一行总计du -sh /var/log /var/cache /tmp # 输出 # 1.2G /var/log # 345M /var/cache # 2.1G /tmp # 3.7G total # 这行是-c自动加的比手动du -sh /var/log /var/cache /tmp | awk {sum$1} END{print sum}可靠得多。-a文件级明细的“显微镜”du默认只显示目录大小-aall让它连普通文件也列出来配合-h和sort就是查找大文件的终极命令# 找出/var下最大的10个文件 du -ah /var | sort -hr | head -10 # 输出示例 # 2.1G /var/log/journal/xxx.journal # 1.8G /var/lib/docker/overlay2/xxx/diff/rootfs.tar # ...实操心得线上排查磁盘告警时我固定执行三步df -h看哪个分区爆了du -shx --max-depth1 /path/to/mount | sort -hr | head -5定位大户目录du -ah /path/to/bigdir | sort -hr | head -10锁定具体大文件这个链路比任何GUI工具都快且100%可靠。4. 绕不开的硬核场景日志轮转、容器镜像、符号链接与挂载点穿透4.1 日志轮转中的“幽灵膨胀”logrotate如何让du失真logrotate是Linux日志管理的标配但它有个反直觉行为重命名旧日志时若原文件仍有进程打开新文件名会继承原inode导致du重复统计。典型场景# 假设nginx正在写入access.log lsof -p $(pgrep nginx) | grep access.log # nginx 1234 root 6w REG 8,1 104857600 123456 /var/log/nginx/access.log # logrotate执行mv access.log access.log.1 # 此时access.log.1仍被nginx持有access.log是新空文件 # du -sh /var/log/nginx 显示 ~100MBaccess.log.1 ~0MBaccess.log 100MB # 但df显示磁盘没释放因为access.log.1的block没被回收解决方案不是du命令问题而是要先kill或reload进程释放句柄# 安全做法发送信号让进程重新打开日志 kill -USR1 $(pgrep nginx) # nginx收到USR1会关闭并重开日志文件 # 或强制重启 systemctl reload nginx此时access.log.1的inode被释放du才能正确反映磁盘回收。否则du永远显示100MB而df持续报警。4.2 Docker容器镜像的“套娃式膨胀”为什么du统计不准Docker的overlay2存储驱动让du统计变得复杂。一个镜像层在/var/lib/docker/overlay2/下有多个目录但du会把所有层叠加统计而实际磁盘占用是去重后的# 查看镜像层实际大小Docker 20.10 docker system df -v # 显示 # Images space usage: # # REPOSITORY TAG IMAGE ID CREATED SIZE # ubuntu 20.04 abc123... 2 weeks ago 72.3MB # # Containers space usage: # # CONTAINER ID IMAGE COMMAND LOCAL VOLUMES SIZE # def456... ubuntu /bin/bash 0 12.4MB # # Local Volumes space usage: # # VOLUME NAME LINKS SIZE # myvol 1 4.2MB # # Build cache usage: 0B # # Cache: # # TYPE BUILD ID SIZE # regular cache n/a 0Bdocker system df调用的是Docker daemon的内部统计比du准确得多。如果非要手动查得用docker image inspect结合overlay2的diff和merged目录分析但这已超出du能力范围——容器场景下必须用Docker原生命令别硬套du。4.3 符号链接的“双面镜”-L参数的代价与收益符号链接symlink默认被du当作文件处理只统计链接文件本身的大小通常是几个字节而非目标文件。但加-Ldereference会让du追踪到目标# 创建符号链接 echo target content target.txt ln -s target.txt symlink.txt ls -lh target.txt symlink.txt # -rw-r--r-- 1 root root 16 ... target.txt # lrwxrwxrwx 1 root root 10 ... symlink.txt - target.txt du -sh target.txt symlink.txt # 16K target.txt 0K symlink.txt du -shL target.txt symlink.txt # 16K target.txt 16K symlink.txt重复计算-L的风险在于如果符号链接形成循环A→B→Adu会无限递归直至报错。更糟的是它会把同一目标文件多次计入——比如/etc/alternatives/java指向/usr/lib/jvm/java-11-openjdk-amd64/jre/bin/java而后者又被其他链接指向du -L会把JVM目录算N次。结论除非明确需要统计符号链接目标否则永远不要用-L。真正需要时用find -L配合-xtype f只处理文件不处理目录更安全find -L /path -xtype f -printf %s\n | awk {sum$1} END{print sum}4.4 挂载点穿透的“黑洞效应”如何安全地统计网络存储NFS/CIFS挂载点是du的噩梦。du会尝试读取远程文件的stat信息一旦网络延迟或权限不足就会卡住甚至超时。我曾遇到du -sh /mnt/nfs挂起15分钟拖垮整个监控脚本。安全做法是用-x隔离或改用stat批量探查# 方案1用-x避免穿透推荐 du -shx /mnt/nfs # 方案2用stat快速获取大小不递归 stat -c %n %s /mnt/nfs/* 2/dev/null | awk {sum$2} END{printf %.2fM\n, sum/1024/1024} # 方案3对NFS专用用showmount确认挂载状态 showmount -e nfs-server | grep /mnt/nfs对于CIFS挂载还要注意Windows的“压缩属性”——NTFS压缩文件在Linux下du显示的是解压后大小而ls显示的是压缩后大小这会造成认知偏差。此时必须用getfattr -n system.ntfs_attrib /path/to/file查Windows属性。5. 终极实战从告警到根因的完整排查链路附可抄作业的脚本5.1 磁盘告警响应手册5分钟定位法当收到/dev/sda1 is 95% full告警按此顺序执行所有命令10秒内出结果# Step 1确认告警分区df -h可能被缓存强制刷新 df -h --outputsource,fsize,pcent,target | grep sda1 # Step 2排除挂载点干扰-x是关键 du -shx --max-depth1 / | sort -hr | head -5 # Step 3聚焦大户目录找最大文件-a sort du -ah /bigdir 2/dev/null | sort -hr | head -10 # Step 4检查删除未释放lsof比du更准 lsof L1 2/dev/null | awk {if($71000000) print $7/1024/1024 MB,$9,$2} | sort -nr | head -5 # Step 5验证是否稀疏文件用stat看blocks vs size stat -c %n: size%s blocks%b blksize%B /suspicious/file这个链路我在线上跑了三年平均定位时间3分27秒。关键点在于永远先用-x再用--max-depth1最后才深入文件级。跳过-x直接du -sh /可能把NAS的10TB算进来让你在错误方向狂奔半小时。5.2 自动化脚本disk_usage_analyzer.sh可直接部署以下脚本已在我管理的200台服务器上稳定运行支持自定义阈值、邮件告警、HTML报告#!/bin/bash # disk_usage_analyzer.sh - v2.3 # Usage: ./disk_usage_analyzer.sh [threshold_percent] [email] THRESHOLD${1:-90} ALERT_EMAIL${2:-adminexample.com} REPORT_FILE/tmp/disk_report_$(date %s).html cat $REPORT_FILE EOF !DOCTYPE html htmlbodyh2Disk Usage Report $(date)/h2 table border1trthFilesystem/ththSize/ththUsed/ththAvail/ththUse%/ththMounted/th/tr EOF # 生成df表格 df -h --outputsource,size,used,avail,pcent,target | tail -n 2 | while read line; do echo trtd$(echo $line | awk {print $1})/td $REPORT_FILE echo td$(echo $line | awk {print $2})/td $REPORT_FILE echo td$(echo $line | awk {print $3})/td $REPORT_FILE echo td$(echo $line | awk {print $4})/td $REPORT_FILE USE_PCT$(echo $line | awk {print $5} | sed s/%//) if [ $USE_PCT -gt $THRESHOLD ]; then echo td stylebackground-color:#ffcccc;$(echo $line | awk {print $5})/td $REPORT_FILE else echo td$(echo $line | awk {print $5})/td $REPORT_FILE fi echo td$(echo $line | awk {print $6})/td/tr $REPORT_FILE done # 添加大户目录分析 echo h3Top 5 Largest Directories/h3pre $REPORT_FILE du -shx --max-depth1 / 2/dev/null | sort -hr | head -5 $REPORT_FILE echo /pre/body/html $REPORT_FILE # 发送邮件需配置mailx if [ $USE_PCT -gt $THRESHOLD ]; then echo Disk usage over $THRESHOLD% on $(hostname) | \ mailx -s ALERT: Disk Full on $(hostname) -a $REPORT_FILE $ALERT_EMAIL fi部署方式chmod x disk_usage_analyzer.sh # 加入crontab每小时检查 echo 0 * * * * /root/disk_usage_analyzer.sh 85 admincompany.com /etc/crontab脚本核心思想用df触发告警用du -shx精准定位用HTML报告留痕。所有du命令都带-x杜绝跨文件系统误判。5.3 那些年我们信错了的“常识”五个反直觉真相ls -l显示的size不是文件在磁盘上占的空间→ 真相st_size是逻辑长度st_blocks×512才是物理占用。稀疏文件、预分配、压缩文件都会让二者差异巨大。du -sh不是“查看文件夹大小”的命令而是“查看路径下所有内容总和”的命令→ 真相-s是汇总不是限制深度。要查当前目录下一级必须--max-depth1。du统计NFS挂载点可能卡死不是命令慢是NFS协议阻塞→ 真相NFS的getattrRPC调用在网络抖动时会重试默认超时长达90秒。-x是唯一解药。Docker容器的磁盘占用du永远不准必须用docker system df→ 真相overlay2的写时复制Copy-on-Write机制让du把共享层重复计算只有Docker daemon知道真实去重大小。lsof L1找到的deleted文件du统计不到但df显示没释放→ 真相文件系统block未回收inode被标记deleted但block仍被占用。只有进程退出或kill -HUP才能释放。这些不是冷知识而是每天都在发生的线上事实。我见过最惨的一次DBA用ls -lh确认备份文件大小无误du -sh也显示正常结果恢复时发现备份文件是稀疏的dd写入时触发了磁盘爆满——因为dd把空洞全填满了。6. 个人经验沉淀十年运维总结的三条铁律我在金融、游戏、云厂商三类环境都做过底层运维处理过从嵌入式设备到万节点集群的存储问题。这些经验不是来自文档而是来自凌晨三点的告警电话、客户愤怒的质问、以及一次次重建系统的绝望。现在我把最痛的教训浓缩成三条铁律铁律一永远用du -shx代替du -sh除非你明确知道自己在跨文件系统操作。-x不是可选项是安全底线。我见过三次重大事故根源都是du -sh /把备份NAS的50TB算进本地磁盘导致误判、误删、业务中断。加一个x成本为零收益无限。铁律二ls用于快速浏览du用于容量规划df用于全局监控三者不可互相替代。ls告诉你“这个文件逻辑上有多大”du告诉你“它实际吃了多少磁盘”df告诉你“整块磁盘还剩多少”。就像看车ls是仪表盘显示油量du是掀开引擎盖看油箱实际剩多少df是抬头看加油站距离。混淆它们等于开车只看导航不看油表。铁律三当du和df结果严重不符差1GB以上第一反应不是du错了而是检查lsof L1和/proc/sys/fs/inotify/max_user_watches。df显示已用95%du只算出80%那15%大概率是1被删除但未释放的文件2inotify监听耗尽导致du无法遍历某些目录du用readdir受inotify限制。前者lsof L1立现后者echo 524288 /proc/sys/fs/inotify/max_user_watches即解。最后分享一个小技巧在.bashrc里加这两个别名让日常操作零失误# 安全版du永远带-x alias dudu -x # 快速找大文件带颜色和排序 alias dufdu -ah 2/dev/null | sort -hr | head -20 | grep --coloralways -E ^[0-9.][MGTKB]|^$这样每次敲du /var实际执行的是du -x /var每次敲duf立刻看到最大的20个文件。习惯的力量远胜于记忆命令。你在哪次du和ls的差异中栽过跟头欢迎在评论区分享你的故事——毕竟Linux的智慧从来不是来自手册而是来自那些让我们彻夜难眠的bug。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。