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 文件适合进一步分析或做可视化处理。
这种工具非常适合大规模数据环境(科研服务器、日志归档目录、数据湖存储等),能帮你快速定位大文件夹的“罪魁祸首”。 🚀