Führ du -sh auf einem Ordner aus und vergleiche das Ergebnis mit der Größe im Finder: Die beiden Werte weichen oft voneinander ab, manchmal um Gigabytes. Keiner von beiden ist falsch. Sie beantworten unterschiedliche Fragen: wie lang die Dateien sind und wie viel Platz sie auf dem Datenträger belegen. Wenn die Platte voll ist, zählt nur die zweite, und sie richtig zu bestimmen erfordert mehr, als st_size aufzusummieren.
Die folgenden Regeln befolgt der Disk-Bildschirm der osFoundry-Desktop-App. Ihr Scanner ist eine kurze Rust-Funktion über std::fs, und dieselbe Logik passt in etwa 40 Zeilen Python. Alle Zahlen stammen aus einem Testordner auf einem APFS-Volume mit 4-KB-Allokationsblöcken.
Blöcke zählen, nicht Bytes
st_size ist die Länge der Datei. st_blocks ist die Anzahl der 512-Byte-Einheiten, die das Dateisystem ihr zugewiesen hat, unabhängig von der tatsächlichen Blockgröße des Volumes. Der Scanner der App nutzt standardmäßig Letzteres:
fn allocated_bytes(meta: &fs::Metadata, apparent: bool) -> u64 { if apparent { return meta.len(); } #[cfg(unix)] { use std::os::unix::fs::MetadataExt; meta.blocks().saturating_mul(512) } #[cfg(not(unix))] { meta.len() } } Drei Dateien im Testordner zeigen, warum:
| Datei | st_size | st_blocks × 512 |
|---|---|---|
small.txt (2 Bytes Text) | 2 | 4,096 |
sparse.img (auf 1 GiB gekürzt, nie beschrieben) | 1,073,741,824 | 0 |
big.bin (200 MiB Daten) | 209,715,200 | 209,715,200 |
Eine winzige Datei belegt trotzdem einen ganzen Block, ein Ordner mit 100.000 kleinen Dateien ist auf dem Datenträger also mehrere hundert Megabyte größer als seine Bytezahl. Eine Sparse-Datei ist das Gegenteil: VM-Festplatten, Datenbankdateien und manche Downloads reservieren eine große Länge und belegen nur für die tatsächlich beschriebenen Teile Blöcke. du unter macOS zählt Blöcke; du -A gibt stattdessen die scheinbare Größe aus. Das Kästchen „Apparent size“ in der App schaltet genau darauf um.
Keine Links folgen, das Volume nicht verlassen
Ein Scanner, der symbolischen Links folgt, kann denselben Ordner doppelt zählen oder endlos kreisen, wenn ein Link auf sein eigenes Elternverzeichnis zeigt. Läuft er in ein eingehängtes Laufwerk hinein, addiert er die Dateien eines anderen Datenträgers zur Summe dieses Datenträgers. Die App liest jeden Eintrag mit symlink_metadata, das den Link selbst beschreibt und nicht sein Ziel, und vergleicht die Geräte-IDs mit dem Ordner, bei dem der Scan begann:
if child_meta.file_type().is_symlink() || !same_device(root_meta, &child_meta) { continue; } same_device vergleicht st_dev; das ist dieselbe Prüfung wie bei du -x oder find -xdev.
Hardlinks und Klone
Hardlinks sind eine Datei mit mehreren Namen. Im Testordner zeigt ls -l nach ln big.bin hard.bin zwei Dateien mit je 200 MiB, aber du zählt die Daten nur einmal, weil es sich jedes gesehene Paar (st_dev, st_ino) merkt, sobald st_nlink größer als 1 ist. Jeder Scanner, der Blöcke aufsummiert, sollte das genauso machen. Time-Machine-Backups auf HFS+-Datenträgern und manche Paketmanager verwenden Hardlinks sehr intensiv.
APFS-Klone sind schwieriger. cp -c big.bin clone.bin (oder „Duplizieren“ im Finder) erzeugt eine zweite Datei, die sich die Blöcke des Originals teilt, bis eine der beiden geändert wird. Jede Datei hat ihren eigenen Inode, ihr eigenes st_blocks und einen Link-Zähler von 1, deshalb zählt du für das Paar 400 MiB. Mit df vorher und nachher gemessen, belegte der Klon 0 KB freien Speicher, während ein normales cp derselben Datei 204.984 KB belegte. Kein POSIX-Feld zeigt dieses Teilen an, daher überschätzt eine blockbasierte Summe, wie viel das Löschen eines Klons zurückgibt. Bei Ordnern voller duplizierter Projekte oder Fotobibliotheken ist df vorher und nachher die einzige verlässliche Prüfung.
Eine Python-Version
Dieselben Regeln, nur mit der Standardbibliothek:
import os import stat import sys def usage(root): root_dev = os.lstat(root).st_dev seen = set() allocated = apparent = 0 skipped = 0 stack = [root] while stack: path = stack.pop() try: entries = list(os.scandir(path)) except OSError: skipped += 1 continue for e in entries: try: st = e.stat(follow_symlinks=False) except OSError: skipped += 1 continue if stat.S_ISLNK(st.st_mode) or st.st_dev != root_dev: continue if stat.S_ISDIR(st.st_mode): stack.append(e.path) continue if st.st_nlink > 1: key = (st.st_dev, st.st_ino) if key in seen: continue seen.add(key) allocated += st.st_blocks * 512 apparent += st.st_size return allocated, apparent, skipped if __name__ == "__main__": alloc, appar, skipped = usage(sys.argv[1]) print(f"on disk {alloc / 2**30:8.2f} GiB") print(f"apparent {appar / 2**30:8.2f} GiB") if skipped: print(f"skipped {skipped} unreadable entries") Auf dem Testordner gibt es 0,39 GiB auf dem Datenträger und 1,39 GiB scheinbare Größe aus, was du -sk (409.604 KB) und du -sAk entspricht. Bei einem 14 GiB großen Quellcode-Baum stimmt es mit du -sk auf zwei Nachkommastellen überein. Behalte die Anzahl der übersprungenen Einträge: Unter macOS lassen sich durch Datenschutzberechtigungen geschützte Ordner (Mail, Nachrichten, Safari) ohne Festplattenvollzugriff nicht lesen, und eine Summe, die sie stillschweigend weglässt, wirkt vollständiger, als sie ist.
Die App hält das Ergebnis klein genug zum Zeichnen. Jeder Ordner behält seine 36 größten Kinder, der Rest wird in einem einzigen Knoten „Other items“ aufsummiert; wer also einen node_modules-Ordner mit tausend Paketen öffnet, sieht 36 Kästchen und eines für alles andere statt tausend Splitter.
Sicher löschen
Das eigentliche Risiko eines Datenträger-Tools liegt im Löschschritt. Die App verschiebt Elemente mit dem Crate trash in den System-Papierkorb, statt sie per unlink zu entfernen, und prüft jeden Pfad, bevor sie etwas verschiebt:
if !target.starts_with(&root) || target == root || home.as_ref() == Some(&target) { return Err("Cannot move a scan root or home folder to Trash".into()); } if mount_points .iter() .any(|mount| target == *mount || target.parent() == Some(mount.as_path())) { return Err("Cannot move a volume or its top-level items to Trash".into()); } Pfade werden zuerst kanonisiert, damit .. oder ein symbolischer Link kein Ziel außerhalb des gescannten Ordners einschmuggeln kann. Eine Auswahl, in der ein markierter Ordner einen anderen enthält, wird ebenfalls abgelehnt: Sobald das Elternverzeichnis im Papierkorb liegt, existiert der Pfad des Kindes nicht mehr, und der Stapel würde auf halber Strecke scheitern.
Die Desktop-App ist auf der osFoundry-Download-Seite kostenlos erhältlich.



