
1. 项目概述从“磁盘空间去哪儿了”说起作为一名在Linux世界里摸爬滚打多年的运维和开发者我几乎每天都会和磁盘空间打交道。服务器报警磁盘空间不足开发环境编译失败提示“No space left on device”或者只是想清理一下个人目录第一个浮现在脑海的命令十有八九是du。这个看似简单的命令却是我们洞察存储使用情况的“眼睛”。然而我发现很多朋友对du的使用还停留在du -sh *的层面面对复杂的目录结构、需要排序或过滤特定文件时就显得力不从心或者写出冗长低效的脚本。今天我们就来彻底拆解du命令不仅讲透它的每一个常用选项和组合拳更重要的是我会分享如何用 Shell 和 Python 去实现du的核心逻辑。理解其原理不仅能让你在命令行中游刃有余更能提升你解决实际存储问题的底层能力。无论你是刚接触 Linux 的新手还是希望优化运维脚本的老兵这篇详解都能让你对磁盘空间管理有全新的认识。2.du命令核心机制与选项全解du全称 “disk usage”用于估算文件和目录占用的磁盘空间。它的核心思想是递归地统计指定路径下所有文件的大小并汇总。这里有一个关键概念磁盘占用空间通常不等于文件大小。因为文件系统有“块大小”Block Size通常为 4KB的概念即使一个文件只有 1 字节它也会占用一个整块4KB。du默认报告的就是这个占用空间这也是为什么du报告的值通常略大于ls -l看到的文件大小之和。2.1 基础选项你的瑞士军刀让我们从最常用、必须掌握的选项开始。记住du的默认行为是递归显示目录及其所有子目录的磁盘使用情况单位是 KB。-h或--human-readable这是使用频率最高的选项之一。它以人类易读的形式K、M、G、T显示大小而不是一长串数字。例如2048会显示为2.0K1048576显示为1.0G。强烈建议在任何时候都加上-h除非你在进行脚本化的数值比较。-s或--summarize仅显示指定目录的总用量而不列出其内部每个子项。当你只关心一个目录总共占用了多少空间时这个选项能避免输出信息过载。du -sh /home就是一个经典组合用于快速查看/home目录的总大小。-c或--total在最后一行显示所有参数的总计。当你同时查看多个目录并想知道它们的空间总和时非常有用。例如du -shc /var/log /var/cache会分别显示两个目录的大小并在最后一行显示total。--max-depthN控制显示的深度。这是管理复杂目录结构的利器。--max-depth0等同于-s只显示总计。--max-depth1显示指定目录及其直接子目录的大小。这是分析目录空间分布的黄金命令du -h --max-depth1 /path/to/dir。你可以指定任意深度 N来查看更细粒度的分布。注意-h和--max-depth是好朋友但和某些排序操作在脚本中直接配合时需要小心因为-h输出的是带单位的字符串不利于机器排序。对于脚本可以先使用不带-h的du获取原始 KB 数值进行处理。2.2 进阶选项与组合技巧掌握了基础我们来看看如何用du解决更复杂的问题。-a或--all不仅显示目录也显示每一个文件的大小。在需要定位具体大文件时非常有用。可以结合--max-depth使用例如du -ah --max-depth2 /path查看两层深度内的所有文件和目录。--excludePATTERN排除符合 PATTERN支持通配符的文件或目录。这是清理和排查时的神器。例如你想统计一个项目目录的大小但排除所有的.git目录和.log文件du -sh --exclude.git --exclude*.log /project。-x或--one-file-system仅统计与指定参数在同一文件系统上的内容。如果你在/目录下执行du它默认会跨文件系统统计/home、/boot等挂载点。加上-x可以避免这种情况让你只关注当前文件系统例如根分区的使用情况。-t, --thresholdSIZE仅显示大小超过或使用-t配合--apparent-size时等于SIZE 的条目。这对于寻找“大块头”非常直接。例如du -ah -t 100M /会找出根目录下所有大于 100MB 的文件和目录。一个强大的组合实例找出/var目录下最大的 5 个目录深度为 1。du -h --max-depth1 /var 2/dev/null | sort -hr | head -n 6解释du -h --max-depth1 /var获取/var下所有一级子目录的大小人类可读。2/dev/null将du命令可能遇到的“权限不足”等错误信息丢弃避免污染输出。sort -hr-h参数让sort能正确排序人类可读的数值如 2K, 1G-r是反向排序即从大到小。head -n 6取前6行第一行通常是/var自己的总计加上后面5个子目录。2.3--apparent-size与-b/-k/-m/-g的差异这是容易混淆的一点。--apparent-size报告文件的表观大小即ls -l看到的大小也就是文件内容的实际字节数不考虑文件系统的块开销。-b、-k、-m、-g这些选项强制du以指定单位Bytes, Kilobytes, Megabytes, Gigabytes输出磁盘占用空间。注意-k是默认选项。关键区别一个 1 字节的文件du -b可能报告 4096因为占了一个 4KB 的块而du --apparent-size -b会报告 1。在需要精确计算数据量如网络传输时用--apparent-size在关心磁盘实际消耗时用默认行为或-b/-k等。3. 从原理到实现动手编写一个简易du理解了du的选项我们深入其核心尝试用代码实现它的基本功能。这不仅能巩固对命令的理解更能锻炼文件系统编程能力。我们将分别用 Shell 和 Python 实现一个简化版的du -s仅统计总大小。3.1 Shell 脚本实现深度优先遍历Shell 脚本实现的核心是递归遍历目录。这里我们实现一个mydu函数模拟du -sb以字节为单位显示总计的行为。#!/bin/bash # 函数计算目录的磁盘使用量字节类似 du -sb function mydu() { local dir$1 local total_size0 # 安全检查参数是否存在且为目录 if [[ ! -e $dir ]]; then echo Error: Path $dir does not exist. 2 return 1 fi if [[ ! -d $dir ]]; then # 如果是文件直接返回其大小磁盘占用取块大小整数倍 local file_size$(stat -c %s $dir) # 获取表观大小 local block_size$(stat -c %B $dir) # 获取块大小通常是512或4096字节 # 计算占用的块数并转换为字节 local blocks$(stat -c %b $dir) echo $(( blocks * block_size )) return 0 fi # 递归处理目录内的每一项 for item in $dir/*; do # 处理通配符未匹配的情况 [[ -e $item ]] || continue if [[ -d $item ]]; then # 如果是目录递归调用 sub_size$(mydu $item) total_size$(( total_size sub_size )) else # 如果是文件使用 stat 获取其占用的磁盘块信息 local blocks$(stat -c %b $item 2/dev/null) local block_size$(stat -c %B $item 2/dev/null) if [[ -n $blocks -n $block_size ]]; then total_size$(( total_size blocks * block_size )) fi fi done # 最后加上目录项本身占用的元数据空间这里简化处理通常很小可忽略或通过stat获取 # 为简化我们假设目录元数据占用为0或通过统计其自身属性获得 # 实际上du会统计目录节点本身占用的空间。我们可以用类似文件的方法 local dir_blocks$(stat -c %b $dir 2/dev/null) local dir_block_size$(stat -c %B $dir 2/dev/null) if [[ -n $dir_blocks -n $dir_block_size ]]; then total_size$(( total_size dir_blocks * dir_block_size )) fi echo $total_size } # 主程序 if [[ $# -eq 0 ]]; then target. else target$1 fi size_in_bytes$(mydu $target) # 转换为人类可读格式简易版 if [[ $size_in_bytes -ge 1073741824 ]]; then printf %.2fG\n $(echo $size_in_bytes / 1073741824 | bc -l) elif [[ $size_in_bytes -ge 1048576 ]]; then printf %.2fM\n $(echo $size_in_bytes / 1048576 | bc -l) elif [[ $size_in_bytes -ge 1024 ]]; then printf %.2fK\n $(echo $size_in_bytes / 1024 | bc -l) else printf %dB\n $size_in_bytes fi脚本解析与注意事项递归陷阱脚本使用了深度优先递归。对于非常深或链接环虽然已通过-e检查部分避免的目录结构可能导致栈溢出。生产环境的du命令使用系统调用和栈管理来避免这个问题。stat命令-c %b获取分配的块数-c %B获取块大小字节。这是计算磁盘占用空间的关键。-c %s获取的是表观大小。性能这个脚本为每个文件都调用一次stat在文件数量巨大时性能远不如C语言实现的原生du命令。原生du会使用fts文件树遍历系列函数进行更高效的遍历。符号链接此脚本没有特殊处理符号链接。原版du默认不跟踪符号链接-L选项会跟踪。如果需要可以在遍历前用-L选项检查stat或使用readlink -f。错误处理我们对stat命令进行了静默错误处理2/dev/null因为对某些无权限访问的文件stat会失败。原版du会报告这些错误。3.2 Python 实现更清晰的对象模型用 Python 实现可以更清晰地组织代码并利用os模块。这里我们实现一个类同时支持磁盘占用和表观大小的统计。#!/usr/bin/env python3 import os import sys from pathlib import Path class SimpleDU: def __init__(self, follow_symlinksFalse): self.follow_symlinks follow_symlinks # 缓存文件系统的块大小假设统一简化处理 self.block_size os.statvfs(.).f_bsize if hasattr(os, statvfs) else 4096 def get_disk_usage(self, path): 返回路径的磁盘占用空间字节 try: stat_info os.stat(path, follow_symlinksself.follow_symlinks) except (OSError, PermissionError): # 模拟du的行为跳过无权限访问的条目 return 0 # st_blocks 是512字节块的数量需要转换 # 注意os.stat 返回的 st_blocks 单位是 512 字节无论实际块大小是多少 return stat_info.st_blocks * 512 def walk_and_sum(self, start_path): 递归遍历并汇总磁盘使用量 total_usage 0 start_path Path(start_path).resolve() # 解析为绝对路径 if not start_path.exists(): print(fError: Path {start_path} does not exist., filesys.stderr) return 0 if start_path.is_file() or (self.follow_symlinks and start_path.is_symlink()): # 如果是文件或跟踪的链接直接返回其占用 return self.get_disk_usage(start_path) # 遍历目录 try: entries list(start_path.iterdir()) except (PermissionError, OSError): # 无法读取目录返回目录自身的元数据占用 return self.get_disk_usage(start_path) for entry in entries: try: if entry.is_dir() and not entry.is_symlink(): total_usage self.walk_and_sum(entry) else: # 文件、符号链接等 total_usage self.get_disk_usage(entry) except (PermissionError, OSError): # 跳过无法访问的条目 continue # 加上当前目录本身的元数据占用 total_usage self.get_disk_usage(start_path) return total_usage def format_size(self, bytes_size): 将字节数格式化为人类可读字符串 for unit in [B, K, M, G, T]: if bytes_size 1024.0: return f{bytes_size:3.1f}{unit} bytes_size / 1024.0 return f{bytes_size:.1f}P if __name__ __main__: import argparse parser argparse.ArgumentParser(descriptionA simple du implementation.) parser.add_argument(path, nargs?, default., helpTarget directory (default: current dir)) parser.add_argument(-L, actionstore_true, helpfollow symbolic links (like du -L)) parser.add_argument(--apparent-size, actionstore_true, helpprint apparent sizes (not implemented in this simple version)) args parser.parse_args() du SimpleDU(follow_symlinksargs.L) total_bytes du.walk_and_sum(args.path) print(f{du.format_size(total_bytes)}\t{args.path})代码实现要点与避坑指南st_blocks的坑Python 的os.stat()返回的st_blocks属性其单位固定是 512 字节而不是文件系统的实际块大小。这是 Unix 历史遗留问题。所以计算磁盘占用的正确方法是stat_info.st_blocks * 512。这是很多自行实现du时容易出错的地方。Pathlib的优势使用pathlib.Path对象让路径操作更安全、直观避免了字符串拼接可能带来的错误。递归与性能和 Shell 版本一样深度递归有风险。对于生产环境可以考虑使用os.scandir()比os.listdir()更高效因为它返回DirEntry对象通常包含 stat 信息进行迭代并使用栈stack或队列queue来管理遍历实现广度优先或非递归遍历。权限处理必须妥善处理PermissionError和OSError模拟du命令跳过无权限访问项的行为而不是让程序崩溃。符号链接通过follow_symlinks参数控制是否跟踪使用Path.is_symlink()和os.stat(follow_symlinks...)来正确判断和处理。--apparent-size上述简易版未实现此功能。若要实现需要修改get_disk_usage方法当需要表观大小时直接返回stat_info.st_size。4. 实战场景与高阶组合应用理解了原理和基础实现后我们来看看du在真实工作流中如何大显身手。4.1 场景一定位磁盘空间消耗的“元凶”这是最常见的场景。服务器/空间告急你需要快速找到是哪个目录或文件在“野蛮生长”。高效排查流程全局概览首先使用df -h确认是哪个挂载点空间不足。逐层深入假设是根分区从根目录开始逐层使用du -h --max-depth1 path | sort -hr向下钻取。# 第一层查看根目录下哪个一级目录最大 sudo du -h --max-depth1 / 2/dev/null | sort -hr | head -20 # 输出可能显示 /var 最大 # 第二层进入 /var sudo du -h --max-depth1 /var 2/dev/null | sort -hr | head -20 # 输出可能显示 /var/log 最大 # 第三层进入 /var/log并查看大文件 sudo du -ah /var/log 2/dev/null | sort -hr | head -30处理特定类型文件如果发现是日志文件如*.log可以使用truncate或 file.log清空确保服务支持或配置日志轮转logrotate。如果是缓存文件检查是否可以安全删除。实操心得使用sudo是因为很多系统目录普通用户无权读取。2/dev/null过滤错误信息让输出更干净。务必谨慎操作sudo rm尤其是在根目录下。建议先ls -lh确认文件或使用rm -i交互删除。4.2 场景二统计特定类型文件的总大小例如你想知道一个项目里所有.py文件总共占了多少空间或者所有图片文件*.jpg, *.png的存储成本。# 方法1使用 find 结合 du find /path/to/project -type f -name *.py -exec du -cb {} | tail -1 # 解释find 找到所有.py文件-exec ... 将它们一次性传给 du -cbdu计算每个文件的大小-c和总字节数-btail -1 取最后一行总计。 # 方法2使用 find 结合 awk更高效避免为每个文件启动 du find /path/to/project -type f -name *.jpg -o -name *.png -printf %s\n | awk {sum$1} END {print sum} # 解释find 的 -printf %s\n 直接输出每个文件的字节大小awk 累加并打印总和。这种方法性能最好。4.3 场景三监控目录增长与定时清理结合crontab和du可以实现简单的目录空间监控。#!/bin/bash # 脚本monitor_dir_size.sh TARGET_DIR/var/log/myapp THRESHOLD_MB1024 # 1GB LOG_FILE/var/log/du_monitor.log current_size_mb$(du -sm $TARGET_DIR | cut -f1) timestamp$(date %Y-%m-%d %H:%M:%S) echo [$timestamp] Size of $TARGET_DIR: ${current_size_mb}MB $LOG_FILE if [[ $current_size_mb -gt $THRESHOLD_MB ]]; then echo [$timestamp] ALERT: $TARGET_DIR exceeds ${THRESHOLD_MB}MB! Current: ${current_size_mb}MB $LOG_FILE # 触发清理动作例如find /var/log/myapp -name *.log.? -mtime 7 -delete # 或者发送告警邮件/消息 fi然后将此脚本加入 crontab例如每小时运行一次0 * * * * /path/to/monitor_dir_size.sh。4.4 场景四du与sort,awk,xargs的管道交响曲Linux 命令的强大在于组合。du经常是管道流水线的起点。找出当前目录下最大的10个文件find . -type f -exec du -h {} 2/dev/null | sort -rh | head -10交互式删除大文件非常危险请先在测试环境练习# 找出大于100M的文件并逐一询问是否删除 find . -type f -size 100M -exec du -h {} \; | sort -rh | while read size file; do read -p Delete $file ($size)? [y/N] -n 1 -r echo if [[ $REPLY ~ ^[Yy]$ ]]; then echo Deleting $file... rm -- $file fi done按用户统计家目录使用量适用于多用户环境sudo du -sch /home/* 2/dev/null # 或者更详细地结合 awk 格式化输出 sudo du -sk /home/* 2/dev/null | awk {printf %10.1fG\t%s\n, $1/1024/1024, $2}5. 常见问题、性能调优与替代工具即使对du了如指掌在实际使用中还是会遇到一些坑和性能瓶颈。5.1 常见问题与排查du结果与df显示的总量对不上原因这是最常见的问题之一。df报告的是文件系统级别的磁盘使用情况包括元数据inode表、日志等、保留块通常5%给root以及已删除但仍被进程占用的文件lsof | grep deleted。du只统计当前可见文件的总和。排查如果df显示使用率高而du统计小可以检查是否有僵尸文件lsof L1或lsof | grep deleted。也可以使用tune2fs -l /dev/sdXX | grep Reserved查看保留块空间。du执行速度慢尤其是挂载了NFS或大量小文件时原因du需要 stat 每个文件I/O 操作是主要瓶颈。网络文件系统NFS延迟更高。海量小文件会导致 stat 系统调用次数爆炸。优化使用-x避免扫描其他文件系统。使用--exclude跳过已知的非目标目录如.git,node_modules。对于 NFS可以考虑在服务器端执行du并缓存结果如果支持或者使用基于快照的统计。终极方案使用文件系统自带的配额quota或树状使用量统计工具如ncdu下文介绍。du: cannot access ‘./.snapshot’: Permission denied之类的错误刷屏处理这是正常现象说明你对该路径下的某些文件或目录没有读取权限。使用2/dev/null重定向标准错误来屏蔽这些信息du -sh /path 2/dev/null。如果确实需要知道哪些路径被跳过可以将错误重定向到文件du -sh /path 2errors.log。5.2 性能调优与替代方案当du成为瓶颈时可以考虑以下方案使用ncdu(NCurses Disk Usage)这是一个基于文本界面的交互式du工具。它先扫描整个目录将结果保存在内存中然后允许你快速浏览、排序、删除。强烈推荐用于交互式分析。安装apt install ncdu或yum install ncdu。使用ncdu /path/to/scan。使用dust一个用 Rust 编写的更直观的du替代品默认输出树状图视觉效果更佳。安装可通过 cargocargo install du-dust。文件系统特性一些现代文件系统如 ZFS, Btrfs支持原生的快照和空间使用报告速度极快。例如 ZFS 的zfs list -o space。提前缓存对于变化不频繁的目录可以定期如每天凌晨运行du并将结果保存到文件需要时直接查看缓存。例如du -sk /home /var/cache/home_usage.dat。5.3 脚本中的注意事项在 Shell 脚本中使用du时要特别注意数值比较不要直接比较带-h的输出。应该使用-kKB或-b字节获取纯数字进行比较。# 错误示例 usage$(du -sh /dir | cut -f1) # 得到如 1.2G 的字符串无法直接比较 # 正确示例 usage_kb$(du -sk /dir | cut -f1) # 得到以KB为单位的整数 if [[ $usage_kb -gt $((1024 * 1024)) ]]; then # 大于1GB echo Directory is larger than 1GB fi跨平台兼容性GNUduLinux 上常见和 BSDdumacOS 上的选项略有不同。例如BSDdu的-h和-d对应--max-depth行为一致但-c是“显示总计”而 GNU 是-c。写可移植脚本时需注意或者明确依赖 GNU coreutils。经过对du命令从使用到原理再到亲手实现的深度剖析你会发现这个简单的命令背后连接着文件系统、操作系统和高效运维的诸多理念。下次当你再键入du时希望你能更清晰地知道它在做什么以及如何让它更好地为你服务。记住工具的价值在于用它解决实际问题而理解工具本身能让你创造出新的解决方案。