🐍 Python 3.10+
Folder Size Report
Walks a directory tree and prints the largest subfolders and files with human-readable sizes, a scriptable WinDirStat alternative.
folder_size_report.py
#!/usr/bin/env python3
"""
folder_size_report.py
Walks a directory tree and prints the top N largest subfolders and
top N largest individual files, with human-readable sizes. Useful
for quickly finding what's eating disk space, similar to WinDirStat
but scriptable and dependency-free (standard library only).
Usage:
python folder_size_report.py "C:\\Users\\me" --top 15
"""
import argparse
import sys
from pathlib import Path
def human_size(num_bytes: float) -> str:
for unit in ("B", "KB", "MB", "GB", "TB"):
if num_bytes < 1024:
return f"{num_bytes:.1f} {unit}"
num_bytes /= 1024
return f"{num_bytes:.1f} PB"
def scan(root: Path):
folder_sizes: dict[Path, int] = {}
file_sizes: list[tuple[Path, int]] = []
for path in root.rglob("*"):
try:
if path.is_file():
size = path.stat().st_size
file_sizes.append((path, size))
parent = path.parent
folder_sizes[parent] = folder_sizes.get(parent, 0) + size
except (PermissionError, OSError):
continue
# Roll sizes up into ancestor folders too
rolled_up: dict[Path, int] = dict(folder_sizes)
for folder, size in folder_sizes.items():
for ancestor in folder.parents:
if root not in ancestor.parents and ancestor != root:
continue
if ancestor == root or root in ancestor.parents:
rolled_up[ancestor] = rolled_up.get(ancestor, 0) + size
if ancestor == root:
break
return rolled_up, file_sizes
def main() -> None:
parser = argparse.ArgumentParser(description="Report largest folders and files under a path.")
parser.add_argument("path", type=Path, help="Root folder to scan")
parser.add_argument("--top", type=int, default=10, help="How many entries to show in each list (default 10)")
args = parser.parse_args()
root = args.path
if not root.is_dir():
sys.exit(f"Error: '{root}' is not a valid folder.")
print(f"Scanning '{root}' ... (this may take a while for large trees)\n")
folder_sizes, file_sizes = scan(root)
print(f"Top {args.top} largest folders:")
for folder, size in sorted(folder_sizes.items(), key=lambda kv: kv[1], reverse=True)[: args.top]:
print(f" {human_size(size):>10} {folder}")
print(f"\nTop {args.top} largest files:")
for file, size in sorted(file_sizes, key=lambda kv: kv[1], reverse=True)[: args.top]:
print(f" {human_size(size):>10} {file}")
if __name__ == "__main__":
main()