Disk Usage – dius

Utility to print largest directories.

Help

dius.py -h
Disk Usage 1.5
usage: dius.py [-h] [-c COUNT] [-s] [-w <12,187>] [directory]

Prints 'COUNT' largest directories found under top 'directory'.

positional arguments:
  directory             set top directory to analyze [/home/petr]

options:
  -h, --help            show this help message and exit
  -c COUNT, --count COUNT
                        set number of largest directories to print [20]
  -s, --silent          suppress progress messages [false]
  -w <12,187>, --width <12,187>
                        set console width for progress indicator [187]

Example

dius.py /etc/
Disk Usage 1.5
Analyzing /etc/
Found 385 directories with 2,592 files in 1 second (385.0 directories/s, 2,592.0 files/s)
 1/385  44.0 MiB /etc/alternatives
 2/385   3.7 MiB /etc/libreoffice/registry
 3/385   1.3 MiB /etc/brltty/Contraction
 4/385 962.0 KiB /etc/dictionaries-common
 5/385 645.7 KiB /etc/ssl/certs
 6/385 640.6 KiB /etc/brltty/Text
 7/385 403.3 KiB /etc/
 8/385 140.0 KiB /etc/apparmor.d/abstractions
 9/385 137.5 KiB /etc/fonts/conf.d
10/385 120.9 KiB /etc/X11/app-defaults
11/385 120.1 KiB /etc/apparmor.d
12/385 117.3 KiB /etc/udev/rules.d
13/385 111.3 KiB /etc/lvm
14/385 106.9 KiB /etc/grub.d
15/385  83.5 KiB /etc/cloud/templates
16/385  82.9 KiB /etc/sane.d
17/385  71.5 KiB /etc/init.d
18/385  69.5 KiB /etc/console-setup
19/385  60.6 KiB /etc/speech-dispatcher/modules
20/385  52.5 KiB /etc/rc2.d
 Other   1.6 MiB
 Total  54.5 MiB

Listing

#!/usr/bin/env python3
# Copyright (c) 2026 ByteDeploy LLP

def dius():
    """Disk Usage"""
    import argparse, colorama, enum, os, string, sys, time

    TITLE = "Disk Usage"
    VERSION = "1.5"
    VERBOSE = False

    COUNT = 20
    MIN_WIDTH = 9 + 0 + 3 # "Scanning " + <no directory> + "..."
    MAX_WIDTH = os.get_terminal_size().columns if sys.stdout.isatty() else 80
    WIDTH = MAX_WIDTH
    WIDTH = min(max(WIDTH, MIN_WIDTH), MAX_WIDTH)

    def now(on="on", at="at"):
        return time.strftime(f"{on + ' ' if on != '' else ''}%Y-%m-%d {at + " " if at != "" else ""}%H:%M:%S")

    def printable(str, max):
        str = "".join([char if char in string.printable else "?" for char in str])
        if len(str) > max: str = str[:max-3] + "..."
        return str

    class Style(enum.Enum):
        plain = enum.auto()
        grouped = enum.auto()
        gazillion = enum.auto()
    STYLE = Style.gazillion

    def plain(num):
        return f"{num}"

    def grouped(num):
        return f"{num:,}"

    def gazillion(num, suffix="B"):
        for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi', 'Ri']:
            if num < 1024:
                return f"{num:.{1 if num % 1 > 0 else 0}f} {unit}{suffix}"
            num /= 1024
        return f"{num:.{1 if num % 1 > 0 else 0}f} {'Qi'}{suffix}"

    def format(num, mode=Style.plain):
        match mode:
            case Style.grouped:
                return grouped(num)
            case Style.gazillion:
                return gazillion(num)
            case Style.plain | _:
                return plain(num)

    def places(num, min=0, mode=Style.plain):
        return max(min, len(format(num, mode)))

    colorama.init()
    print(f"{TITLE} {VERSION}")
    if VERBOSE:
        print('\a', end="")
        print(f"Python {sys.version}")
        print(f"Command '{sys.argv[0]}'")
        print(f"Arguments {sys.argv[1:]}")
        print(f"Executed {now()}")
        start = time.time()

    parser = argparse.ArgumentParser(description="Prints 'COUNT' largest directories found under top 'directory'.")
    parser.add_argument("directory", nargs="?", help="set top directory to analyze [%(default)s]", default=os.getcwd())
    parser.add_argument("-c", "--count", help="set number of largest directories to print [%(default)s]", type=lambda count: max(int(count), 0), default=COUNT)
    parser.add_argument("-s", "--silent", help="suppress progress messages [false]", action = "store_true", default=False)
    parser.add_argument("-w", "--width", help="set console width for progress indicator [%(default)s]", metavar=f"<{MIN_WIDTH},{MAX_WIDTH}>", type=int, choices=range(MIN_WIDTH,MAX_WIDTH+1), default=WIDTH)
    arguments = parser.parse_args()
    directory = arguments.directory
    count = arguments.count
    silent = arguments.silent
    width = arguments.width

    if not silent:
        print(f"Analyzing {directory}")
        BACKTRACK = ("\r" if width < MAX_WIDTH else "\r") if sys.stdout.isatty() else "\n"  # 2nd ? cursor up "\033[A"
    started = time.time()
    usage = {}
    numFiles = 0
    for path, dirs, files in os.walk(directory):
        if not silent:
            print(f"Scanning {printable(path, width-9): <{width-9}}", end=BACKTRACK)
        files = list(filter(os.path.isfile, map(lambda file: os.path.join(path, file), files)))
        numFiles += len(files)
        usage[path] = sum(map(os.path.getsize, files))
    if not silent:
        print(f"         {'': <{width-9}}", end=BACKTRACK)
        seconds = max(1, round(time.time() - started))  # at least 1 second
        dirRate = round(len(usage) / seconds, 1)
        fileRate = round(numFiles / seconds, 1)
        print("Found {} director{} with {} file{} in {} second{} ({} director{}/s, {} file{}/s)".format(
            grouped(len(usage)), "y" if len(usage) == 1 else "ies",
            grouped(numFiles),   ""  if numFiles == 1   else "s",
            grouped(seconds),    ""  if seconds == 1    else "s",
            grouped(dirRate),    "y" if dirRate == 1    else "ies",
            grouped(fileRate),   ""  if fileRate == 1   else "s"))

    usage = sorted(usage.items(), key=lambda pathSize:(- pathSize[1], pathSize[0]))
    widthCount = places(len(usage), min=2, mode=Style.grouped)
    widthIndex = places(count, min=5-1-widthCount, mode=Style.grouped)
    other = sum(map(lambda pathSize: pathSize[1], usage[count:]))
    total = sum(map(lambda pathSize: pathSize[1], usage))
    widthSize = max(
        max(map(lambda pathSize: places(pathSize[1], mode=STYLE), usage[:count])) if count else 0,
        places(other, mode=STYLE),
        places(total, mode=STYLE))
    for i, (path, size) in enumerate(usage[:count]):
        print("{:>{}}/{:>{}} {:>{}} {}".format(
            grouped(i+1), widthIndex,
            grouped(len(usage)), widthCount,
            format(size, mode=STYLE), widthSize,
            path))
    if (0 < count < len(usage)):
        print(f"{'Other':>{widthIndex+1+widthCount}} {format(other, mode=STYLE):>{widthSize}}")
    print(f"{'Total':>{widthIndex+1+widthCount}} {format(total, mode=STYLE):>{widthSize}}")

    if VERBOSE:
        elapsed = time.time() - start
        seconds = round(elapsed)
        minutes, seconds = divmod(seconds, 60)
        hours, minutes = divmod(minutes, 60)
        days, hours = divmod(hours, 24)
        weeks, days = divmod(days, 7)
        print(f"Completed {now()}")
        print(f"Elapsed {weeks:d}w {days:d}d {hours:d}h {minutes:d}m {seconds:d}s ({elapsed:,.3f}s)")
    print('\a', end="")

if '__main__' == __name__:
    dius()
Scroll to Top