Linux 大目录扫描与实时 CSV 导出脚本

Linux 大目录扫描与实时 CSV 导出脚本

在日常运维和科研环境中,我们经常需要快速统计某个目录下的子目录大小,找出占用空间最多的路径,以便进行磁盘清理和资源管理。传统的 du -sh * | sort -h 虽然能用,但缺乏实时刷新、结果筛选和持续记录功能。

本文分享一个基于 Python 的脚本,它调用系统自带的 du 命令完成扫描,并具备以下功能:

  • 实时显示 Top-K 大目录:控制台每隔数秒自动刷新,显示前若干个最大的目录。
  • 周期性写入 CSV:每隔 10 秒将最新结果写入 CSV 文件,避免长时间等待。
  • 结果筛选:只记录超过指定阈值(如 1GB)的目录,减少无效数据。
  • 结果裁剪:最终 CSV 文件仅保留前 2000 条记录,避免文件过大。
  • 友好中断:支持 Ctrl+C 中断,仍会保存最新统计结果。

使用方法

将脚本保存为 dir_scan.py,并赋予可执行权限:

chmod +x dir_scan.py

运行示例:

./dir_scan.py /home -o result.csv -k 20 -i 2 -f 10 -n 2000 -d 8 -m 1073741824

含义说明:

  • -o result.csv:结果保存到 result.csv
  • -k 20:实时显示前 20 个目录
  • -i 2:控制台每 2 秒刷新一次
  • -f 10:每 10 秒写一次 CSV
  • -n 2000:CSV 文件仅保留前 2000 条记录
  • -d 8:最大扫描深度为 8
  • -m 1073741824:仅统计大于等于 1GiB 的目录

代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import sys
import csv
import time
import heapq
import argparse
import subprocess


def iter_du(target: str, depth: int):
    """
    调用 du --max-depth=N --block-size=1,输出目录大小(字节数)。
    每一行格式: <size>\t<path>
    """
    cmd = ["du", f"--max-depth={depth}", "--block-size=1", target]
    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    assert proc.stdout is not None
    for raw in proc.stdout:
        if raw.endswith(b"\n"):
            raw = raw[:-1]
        yield raw

    proc.stdout.close()
    stderr = proc.stderr.read()
    proc.stderr.close()
    ret = proc.wait()
    if stderr:
        sys.stderr.write(os.fsdecode(stderr))
    if ret != 0:
        sys.stderr.write(f"du 退出码 {ret}\n")


def write_csv(path: str, rows, limit: int = 2000):
    """
    写 CSV,只保留前 limit 条
    """
    rows = rows[:limit]
    with open(path, "w", newline="", encoding="utf-8") as f:
        w = csv.writer(f)
        w.writerow(["Size(Bytes)", "Size(GB)", "Path"])
        for size, p in rows:
            w.writerow([size, f"{size/1024**3:.2f}", p])


def main():
    parser = argparse.ArgumentParser(
        description="使用 du --max-depth=N 统计目录大小并导出 CSV(仅 >= 阈值)"
    )
    parser.add_argument("target", nargs="?", default="/home", help="待扫描目录(默认 /home)")
    parser.add_argument("-o", "--output", default="dir_sizes.csv", help="输出 CSV 文件名")
    parser.add_argument("-k", "--topk", type=int, default=20, help="控制台实时显示的 Top-K")
    parser.add_argument("-i", "--interval", type=float, default=2.0, help="控制台刷新间隔秒数")
    parser.add_argument("-m", "--minsize", type=float, default=1024**3, help="筛选阈值(字节,默认 1GiB)")
    parser.add_argument("-d", "--depth", type=int, default=8, help="最大递归深度(默认 8)")
    parser.add_argument("-f", "--flush", type=float, default=10.0, help="写 CSV 的时间间隔(秒,默认 10)")
    parser.add_argument("-n", "--limit", type=int, default=2000, help="CSV 保留的最大行数(默认 2000)")
    args = parser.parse_args()

    target = args.target
    out_csv = args.output
    TOP_K = max(1, args.topk)
    INTERVAL = max(0.1, args.interval)
    MIN_SIZE = int(args.minsize)
    DEPTH = args.depth
    FLUSH_INTERVAL = args.flush
    LIMIT = args.limit

    if not os.path.exists(target):
        print(f"目标路径不存在:{target}", file=sys.stderr)
        sys.exit(2)

    heap_top = []
    results = []
    processed = 0
    start_t = time.time()
    last_refresh = 0.0
    last_flush = 0.0

    try:
        for raw in iter_du(target, DEPTH):
            processed += 1
            try:
                line = os.fsdecode(raw)
            except Exception:
                line = raw.decode("utf-8", "replace")

            parts = line.split("\t", 1)
            if len(parts) < 2:
                continue
            size_str, path = parts
            try:
                size = int(size_str)
            except ValueError:
                continue

            if size >= MIN_SIZE:
                results.append((size, path))
                if len(heap_top) < TOP_K:
                    heapq.heappush(heap_top, (size, path))
                else:
                    if size > heap_top[0][0]:
                        heapq.heapreplace(heap_top, (size, path))

            now = time.time()

            # 控制台刷新
            if now - last_refresh >= INTERVAL:
                os.system("clear")
                print(f"扫描目标: {target} (深度 <= {DEPTH})")
                print(f"已处理 {processed} 条目录,>= {MIN_SIZE} bytes 的目录: {len(results)},耗时 {now - start_t:.1f}s\n")
                top_sorted = sorted(heap_top, key=lambda x: x[0], reverse=True)
                for idx, (s, p) in enumerate(top_sorted, 1):
                    print(f"{idx:2d}. {s/1024**3:10.2f} GB\t{p}")
                last_refresh = now

            # 定期写 CSV
            if now - last_flush >= FLUSH_INTERVAL and results:
                results.sort(key=lambda x: x[0], reverse=True)
                write_csv(out_csv, results, LIMIT)
                print(f"\n已写入 {out_csv} (前 {LIMIT} 条),共 {len(results)} 条记录", file=sys.stderr)
                last_flush = now

    except KeyboardInterrupt:
        print("\n用户中断,正在保存结果 ...", file=sys.stderr)

    # 最终写一次 CSV
    results.sort(key=lambda x: x[0], reverse=True)
    write_csv(out_csv, results, LIMIT)

    total_time = time.time() - start_t
    print(f"\n完成:找到 {len(results)} 个 >= {MIN_SIZE} 字节的目录,已写入 {out_csv}(前 {LIMIT} 条)(耗时 {total_time:.1f}s)")


if __name__ == "__main__":
    main()


效果展示

执行过程中,控制台会定时刷新,展示当前最大目录,例如:

扫描目标: /home (深度 <= 8)
已处理 120345 条目录,>= 1073741824 bytes 的目录: 835,耗时 65.2s

 1.     512.34 GB   /home/data/projectA
 2.     410.12 GB   /home/data/projectB
 3.     207.89 GB   /home/user/videos
 ...

同时,result.csv 文件会在后台不断更新,保证即使扫描尚未结束,也能随时拿到前 2000 条大目录信息。


总结

相比直接用 du 配合 sort,这个脚本的优势在于 实时可视化 + 持续保存

  • 扫描过程不再是黑盒,可以随时观察进度。
  • 即便扫描目录巨大,依然能在过程中获取中间结果。
  • 最终输出的 CSV 文件适合进一步分析或做可视化处理。

这种工具非常适合大规模数据环境(科研服务器、日志归档目录、数据湖存储等),能帮你快速定位大文件夹的“罪魁祸首”。 🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深|码|洞|悉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值