Ejecuta du -sh sobre una carpeta y compáralo con el tamaño del Finder: a menudo no coinciden, a veces por varios gigabytes. Ninguno se equivoca. Responden a preguntas distintas: cuánto miden los archivos y cuánto espacio del disco ocupan. Cuando el disco está lleno solo importa la segunda, y calcularla bien exige algo más que sumar st_size.
Las reglas de abajo son las que sigue la pantalla Disk de la aplicación de escritorio osFoundry. Su escáner es una función corta de Rust sobre std::fs, y la misma lógica cabe en unas 40 líneas de Python. Todos los números salen de una carpeta de prueba en un volumen APFS con bloques de asignación de 4 KB.
Cuenta bloques, no bytes
st_size es la longitud del archivo. st_blocks es el número de unidades de 512 bytes que el sistema de archivos ha asignado, sea cual sea el tamaño de bloque real del volumen. El escáner de la aplicación usa por defecto la segunda:
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() } } Tres archivos de la carpeta de prueba muestran por qué:
| Archivo | st_size | st_blocks × 512 |
|---|---|---|
small.txt (2 bytes de texto) | 2 | 4,096 |
sparse.img (truncado a 1 GiB, nunca escrito) | 1,073,741,824 | 0 |
big.bin (200 MiB de datos) | 209,715,200 | 209,715,200 |
Un archivo diminuto ocupa igualmente un bloque entero, así que una carpeta con 100.000 archivos pequeños ocupa en disco varios cientos de megabytes más de lo que indica su recuento de bytes. Un archivo disperso (sparse) es lo contrario: los discos de máquinas virtuales, los archivos de bases de datos y algunas descargas reservan una longitud grande y solo usan bloques en las partes escritas. El du de macOS cuenta bloques; du -A muestra el tamaño aparente. La casilla Apparent size de la aplicación hace ese mismo cambio.
No sigas enlaces ni salgas del volumen
Un escáner que sigue enlaces simbólicos puede contar dos veces la misma carpeta, o entrar en un bucle infinito con un enlace que apunta a su propio directorio padre. Uno que entra en una unidad montada suma los archivos de otro disco al total de este. La aplicación lee cada entrada con symlink_metadata, que describe el enlace y no su destino, y compara los ID de dispositivo con los de la carpeta donde empezó el escaneo:
if child_meta.file_type().is_symlink() || !same_device(root_meta, &child_meta) { continue; } same_device compara st_dev, que es la misma comprobación que hacen du -x o find -xdev.
Enlaces duros y clones
Los enlaces duros son un único archivo con varios nombres. En la carpeta de prueba, ln big.bin hard.bin hace que ls -l muestre dos archivos de 200 MiB, pero du cuenta los datos una sola vez porque recuerda cada par (st_dev, st_ino) que ya ha visto siempre que st_nlink sea mayor que 1. Cualquier escáner que sume bloques debería hacer lo mismo. Las copias de Time Machine en discos HFS+ y algunos gestores de paquetes dependen mucho de los enlaces duros.
Los clones de APFS son más difíciles. cp -c big.bin clone.bin (o Duplicar en el Finder) crea un segundo archivo que comparte los bloques del original hasta que uno de los dos cambia. Cada archivo tiene su propio inodo, su propio st_blocks y un recuento de enlaces de 1, de modo que du cuenta 400 MiB para el par. Medido con df antes y después, el clon ocupó 0 KB de espacio libre, mientras que un cp normal del mismo archivo ocupó 204.984 KB. Ningún campo POSIX muestra que se comparten bloques, así que un total basado en bloques exagera lo que se recupera al borrar un clon. Para carpetas llenas de proyectos duplicados o bibliotecas de fotos, comparar df antes y después es la única comprobación fiable.
Una versión en Python
Las mismas reglas, solo con la biblioteca estándar:
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") En la carpeta de prueba imprime 0,39 GiB en disco y 1,39 GiB aparentes, lo que coincide con du -sk (409.604 KB) y du -sAk. En un árbol de código fuente de 14 GiB coincide con du -sk hasta dos decimales. Conserva el recuento de entradas omitidas: en macOS, las carpetas protegidas por permisos de privacidad (Mail, Mensajes, Safari) no se pueden leer sin Acceso total al disco, y un total que las deja fuera sin avisar parece más completo de lo que es.
La aplicación mantiene el resultado lo bastante pequeño como para dibujarlo. Cada carpeta conserva sus 36 hijos más grandes y el resto se suma en un único nodo "Other items"; así, al abrir una carpeta node_modules con mil paquetes se ven 36 cajas y una más para todo lo demás, no mil rendijas.
Borrar con seguridad
El verdadero riesgo de una herramienta de disco está en el paso de borrar. La aplicación mueve los elementos a la Papelera del sistema con el crate trash en lugar de eliminarlos con unlink, y comprueba cada ruta antes de mover nada:
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()); } Las rutas se canonicalizan primero, de modo que .. o un enlace simbólico no pueden colar un destino fuera de la carpeta escaneada. También se rechaza una selección en la que una carpeta marcada contiene a otra, porque en cuanto la carpeta padre está en la Papelera la ruta del hijo deja de existir y el lote fallaría a medias.
La aplicación de escritorio es gratuita en la página de descarga de osFoundry.




