Linux du 命令深度解析:从磁盘占用统计到目录空间分析

du 的核心原理:递归遍历 + block 计数

du 的本质是统计文件占用的磁盘块数量,而不是文件大小。这两者有微妙但重要的区别。

底层实现通过 stat() 系统调用获取每个文件的 st_blocks 字段:

c 复制代码
// 简化版 du 实现核心逻辑
#include <sys/stat.h>
#include <dirent.h>

off_t calculate_usage(const char *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;
}

关键细节:

  1. st_blocks 是块数,不是字节数 :实际占用 = st_blocks * 512
  2. 稀疏文件处理:一个 10GB 的稀疏文件可能只占用几个块
  3. 目录本身占用:每个目录至少占用 4KB(一个 block)

常用参数详解

1. -h 人类可读格式 (Human-Readable Format)

bash 复制代码
du -h /var/log
# 4.0K  /var/log/apt
# 12M   /var/log/journal
# 128M  /var/log

实现原理:遍历单位数组,找到最合适的单位:

typescript 复制代码
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 `${bytes.toFixed(1)}${units[i]}`
}

2. -s 只显示总计

bash 复制代码
du -sh /home/user
# 2.5G  /home/user

跳过递归输出,只返回顶层统计。

3. --max-depth 控制层级

bash 复制代码
du -h --max-depth=1 /var
# 4.0K  /var/tmp
# 12M   /var/log
# 128M  /var/lib
# 145M  /var

这对于快速定位大目录非常有用。

4. -a 显示所有文件

默认 du 只统计目录,-a 让它也输出每个文件:

bash 复制代码
du -ah /tmp
# 4.0K  /tmp/test.txt
# 8.0K  /tmp/data.json
# 12K   /tmp

5. --exclude 排除特定文件

bash 复制代码
du -h --exclude="*.log" /var
du -h --exclude="node_modules" /home/user/project

支持通配符,可以多次使用排除多种模式。

实战场景与性能优化

场景 1:定位大目录

bash 复制代码
du -h --max-depth=1 /var | sort -hr
# 145M  /var
# 128M  /var/lib
# 12M   /var/log
# 4.0K  /var/tmp

sort -hr 按人类可读格式逆序排序,一眼看出最大的目录。

场景 2:排除特定目录

bash 复制代码
du -sh --exclude="node_modules" --exclude=".git" ~/projects/*

统计项目目录时排除依赖和版本控制目录,避免干扰。

场景 3:查找大文件

结合 find 使用:

bash 复制代码
find /var -type f -size +100M -exec du -h {} \;

或者直接用 du:

bash 复制代码
du -ah /var | grep -E "^[0-9.]+G"

场景 4:监控目录增长

bash 复制代码
watch -n 60 "du -sh /var/log"

每分钟检查 /var/log 的空间占用,适合监控日志增长。

性能考量

1. 大目录遍历优化

du 遍历整个目录树,对于百万文件的目录会很慢。几个优化思路:

  • --one-file-system:不跨文件系统,避免遍历挂载点
  • --threshold:只显示超过指定大小的目录,减少输出
  • 并行处理:GNU parallel 并行统计子目录
bash 复制代码
# 并行统计子目录
find /var -maxdepth 1 -type d | parallel du -sh

2. 硬链接处理

du 默认会多次计数硬链接。使用 -l 参数只计数一次:

bash 复制代码
du -lh /path/to/directory

3. 缓存问题

频繁运行 du 会影响文件系统缓存。可以用 nocache 命令避免:

bash 复制代码
nocache du -sh /large/directory

与 df 的区别

很多人混淆 dudf:

  • df:文件系统级别的空间统计,读取 superblock
  • du:目录/文件级别的空间统计,遍历目录树

典型差异场景:

bash 复制代码
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?

  1. 已删除但仍被占用的文件:进程打开的文件被删除后,空间不会立即释放
  2. 预留空间:ext4 默认预留 5% 给 root
  3. 元数据开销:inode 表、journal 等

查找已删除但仍被占用的文件:

bash 复制代码
lsof +L1 | grep deleted

Web 实现:浏览器端目录统计

通过 File System Access API,可以在浏览器中实现类似功能:

typescript 复制代码
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 会跳过无权限的目录,统计结果可能不完整:

bash 复制代码
du: cannot read directory '/root': Permission denied

使用 sudo 获取完整统计。

2. 符号链接

默认 du 不跟随符号链接。使用 -L 参数跟随:

bash 复制代码
du -Lh /path/to/symlink

3. 稀疏文件误判

bash 复制代码
# 创建稀疏文件
dd if=/dev/zero of=sparse.img bs=1 count=0 seek=10G

ls -lh sparse.img
# -rw-r--r-- 1 user user 10G May 9 22:00 sparse.img

du -h sparse.img
# 0  sparse.img  # 实际占用为 0

ls 显示文件大小,du 显示磁盘占用。

总结

du 命令虽然简单,但背后的设计蕴含了 Unix 哲学:专注做一件事,并把它做好。从 block 计数到递归遍历,从硬链接处理到稀疏文件支持,每个细节都经过精心设计。

下次遇到磁盘空间问题,不妨试试这些组合:

bash 复制代码
# 快速定位大目录
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 磁盘空间监控 | 文件大小转换器

相关推荐
minji...1 小时前
Linux 网络基础(五)守护进程化,前后台进程组,作业,会话,setsid(),daemon(),端口号频繁更换问题
linux·运维·服务器·网络·c++·tcp/ip
AOwhisky1 小时前
Docker 学习笔记:从生态系统到镜像构建
linux·运维·笔记·学习·docker·容器
weixin_446260851 小时前
AI驱动的前沿前端技术栈深度解析:从模型能力到UI封装的完整生命周期
前端·人工智能·ui
CoderMeijun1 小时前
Linux 进程间通信:共享内存详解
linux·共享内存·进程间通信·ipc·shmget
坚持就完事了1 小时前
Ubuntu和Centos中安装软件的命令
linux·ubuntu·centos
江湖有缘1 小时前
Docker部署PortNote端口自动检测工具
运维·docker·容器
程序猿编码1 小时前
Linux 高负载场景下 Web 服务访问日志极速定位工具实现解析(C/C++代码实现)
linux·服务器·c语言·前端·c++
CC城子1 小时前
嵌入式Linux宕机问题GDB调试(二)
linux·gdb
ZC跨境爬虫1 小时前
跟着 MDN 学 HTML day_35:(深入解析 CharacterData 抽象接口)
java·前端·ui·html·edge浏览器·媒体