du 的核心原理:递归遍历 + block 计数
du 的本质是统计文件占用的磁盘块数量,而不是文件大小。这两者有微妙但重要的区别。
底层实现通过 stat() 系统调用获取每个文件的 st_blocks 字段:
// 简化版 du 实现核心逻辑#include<sys/stat.h>#include<dirent.h>off_tcalculate_usage(constchar *path){ struct stat st; if (lstat(path, &st) < 0) return 0; // 目录递归遍历 if (S_ISDIR(st.st_mode)) { DIR *dir = opendir(path); struct dirent *entry; off_t total = 0; while ((entry = readdir(dir)) != NULL) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; char fullpath[PATH_MAX]; snprintf(fullpath, sizeof(fullpath), "%s/%s", path, entry->d_name); total += calculate_usage(fullpath); } closedir(dir); return total + 512; // 目录本身占用 1 个 block } // 文件:返回块数 * 512 字节 return st.st_blocks * 512;}
关键细节:
- st_blocks 是块数,不是字节数
- 稀疏文件处理
- 目录本身占用
常用参数详解
1. -h 人类可读格式 (Human-Readable Format)
du -h /var/log# 4.0K /var/log/apt# 12M /var/log/journal# 128M /var/log
实现原理:遍历单位数组,找到最合适的单位:
function humanReadable(bytes: number): string { const units = ['B', 'K', 'M', 'G', 'T', 'P'] let i = 0 while (bytes >= 1024 && i < units.length - 1) { bytes /= 1024 i++ } return `{units[i]}`}
2. -s 只显示总计
du -sh /home/user# 2.5G /home/user
跳过递归输出,只返回顶层统计。
3. --max-depth 控制层级
du -h --max-depth=1 /var# 4.0K /var/tmp# 12M /var/log# 128M /var/lib# 145M /var
这对于快速定位大目录非常有用。
4. -a 显示所有文件
默认 du 只统计目录,-a 让它也输出每个文件:
du -ah /tmp# 4.0K /tmp/test.txt# 8.0K /tmp/data.json# 12K /tmp
5. --exclude 排除特定文件
du -h --exclude="*.log" /vardu -h --exclude="node_modules" /home/user/project
支持通配符,可以多次使用排除多种模式。
实战场景与性能优化
场景 1:定位大目录
du -h --max-depth=1 /var | sort -hr# 145M /var# 128M /var/lib# 12M /var/log# 4.0K /var/tmp
sort -hr 按人类可读格式逆序排序,一眼看出最大的目录。
场景 2:排除特定目录
du -sh --exclude="node_modules" --exclude=".git" ~/projects/*
统计项目目录时排除依赖和版本控制目录,避免干扰。
场景 3:查找大文件
结合 find 使用:
find /var -type f -size +100M -exec du -h {} \;
或者直接用 du:
du -ah /var | grep -E "^[0-9.]+G"
场景 4:监控目录增长
watch -n 60 "du -sh /var/log"
每分钟检查 /var/log 的空间占用,适合监控日志增长。
性能考量
1. 大目录遍历优化
du 遍历整个目录树,对于百万文件的目录会很慢。几个优化思路:
- –one-file-system
- –threshold
- 并行处理
# 并行统计子目录find /var -maxdepth 1 -type d | parallel du -sh
2. 硬链接处理
du 默认会多次计数硬链接。使用 -l 参数只计数一次:
du -lh /path/to/directory
3. 缓存问题
频繁运行 du 会影响文件系统缓存。可以用 nocache 命令避免:
nocache du -sh /large/directory
与 df 的区别
很多人混淆 du 和 df:
- df:文件系统级别的空间统计,读取 superblock
- du
典型差异场景:
df -h /var# Filesystem Size Used Avail Use% Mounted on# /dev/sda1 50G 45G 5.0G 90% /du -sh /var# 35G /var
为什么 df 显示 45G,du 只有 35G?
- 已删除但仍被占用的文件
- 预留空间
- 元数据开销
查找已删除但仍被占用的文件:
Web 实现:浏览器端目录统计
通过 File System Access API,可以在浏览器中实现类似功能:
async function calculateDirectorySize(dirHandle: FileSystemDirectoryHandle): Promise<number> { let totalSize = 0 for await (const entry of dirHandle.values()) { if (entry.kind === 'file') { const file = await entry.getFile() totalSize += file.size } else if (entry.kind === 'directory') { const subDirHandle = await dirHandle.getDirectoryHandle(entry.name) totalSize += await calculateDirectorySize(subDirHandle) } } return totalSize}// 使用示例const dirHandle = await window.showDirectoryPicker()const size = await calculateDirectorySize(dirHandle)console.log(`Total size: ${formatBytes(size)}`)
注意:浏览器 API 统计的是文件大小,不是磁盘块数,与原生 du 有差异。
常见陷阱
1. 权限不足
du 会跳过无权限的目录,统计结果可能不完整:
du: cannot read directory '/root': Permission denied
使用 sudo 获取完整统计。
2. 符号链接
默认 du 不跟随符号链接。使用 -L 参数跟随:
3. 稀疏文件误判
# 创建稀疏文件dd if=/dev/zero of=sparse.img bs=1 count=0 seek=10Gls -lh sparse.img# -rw-r--r-- 1 user user 10G May 9 22:00 sparse.imgdu -h sparse.img# 0 sparse.img # 实际占用为 0
ls 显示文件大小,du 显示磁盘占用。
总结
du 命令虽然简单,但背后的设计蕴含了 Unix 哲学:专注做一件事,并把它做好。从 block 计数到递归遍历,从硬链接处理到稀疏文件支持,每个细节都经过精心设计。
下次遇到磁盘空间问题,不妨试试这些组合:
# 快速定位大目录du -h --max-depth=1 | sort -hr | head -10# 排除干扰项du -sh --exclude="node_modules" --exclude=".git" *# 监控目录增长watch -n 60 "du -sh /var/log"
希望这篇对你理解 du 命令有帮助。更详细的命令参数可以查看: Linux du 命令参考
相关工具:Linux df 磁盘空间监控 | 文件大小转换器