Measure Directory Space with du Without Misreading the Total

GNU du finds the directories eating your disk and reports it readably, dodging the usual traps around blocks, links and mounted filesystems. This guide walks through finding large directories, checking a result in a human-readable form, and avoiding the common traps. The examples use GNU coreutils 9.4, installed here with the du(1) behaviour documented in August 2026.

Allow about 10 minutes for the basic checks. You need a shell and read access to the directory you are measuring. None of the commands below needs elevated privileges. Use sudo only when permissions prevent a useful reading, and remember that doing so can expose names and sizes you would not otherwise be allowed to inspect.

Checkpoint 1: get a useful overview

Start with one directory and ask for a summary in binary, human-readable units:

du -sh -- /path/to/directory

The final line might look like this:

18G    /path/to/directory

-s prints one total for each argument and -h chooses readable units such as K, M and G. The default unit is 1024 bytes, unless POSIXLY_CORRECT is set, in which case the default is 512 bytes. The command measures filesystem blocks allocated to the files, not simply the number of bytes reported by each file.

Verify which executable and version you are using before comparing output from another machine:

command -v du
du --version | head -n 1

On the version covered here, the second command reports du (GNU coreutils) 9.4.

Checkpoint 2: find the large directories

Remove the summary option and limit the report to the first directory level:

du -h --max-depth=1 -- /path/to/directory

This prints a line for the directory itself and one for each immediate child directory. Sort the numeric values only after asking du for a fixed unit:

du -k --max-depth=1 -- /path/to/directory | sort -n

-k means 1 KiB units. Sorting human-readable values with plain sort -n is a common distraction: 9G and 800M are text, not comparable decimal numbers. If you need readable output after locating the largest numeric entries, run du -h again for the specific paths.

For a deeper but still bounded scan, increase the depth:

du -k --max-depth=2 -- /path/to/directory | sort -n | tail -n 20

A depth of zero is equivalent to a summary. Avoid starting with -a on a large tree: it reports every file as well as directories and can produce a long, slow listing.

Checkpoint 3: distinguish allocated space from file length

Most of the time you want allocated filesystem space. To compare it with the apparent length of files, run both totals:

du -sh -- /path/to/directory
du -sh --apparent-size -- /path/to/directory

The first total includes the blocks used by the filesystem. The second adds file lengths instead. Sparse files can make the apparent total larger than the allocated total, while filesystem overhead and internal fragmentation can make allocated usage larger. The values are not interchangeable with the free-space figure shown by df; du walks named files, whereas df reports the filesystem as a whole.

When an exact byte count is useful, use:

du --bytes --summarize -- /path/to/directory

This is equivalent to apparent size with a block size of one. It still does not turn a sparse file into allocated blocks.

Checkpoint 4: handle links and mount points deliberately

GNU du does not follow symbolic links by default. That prevents a link to a large tree from making the same data appear to belong to the directory being inspected. Use -L only when following every symbolic link is genuinely what you intend:

du -sh --dereference -- /path/to/directory

If only a symbolic link named on the command line should be followed, use -H or -D instead. The default can also be stated explicitly with -P. Be cautious with -L: links can create cycles or lead into unrelated parts of the system.

A directory such as / may contain mounted filesystems. To measure only the filesystem containing the starting path, add -x:

du -xsh -- /

Without -x, the walk can include mounted trees and make a root total look unexpectedly large. This option is a boundary for the walk, not a permission bypass.

Checkpoint 5: exclude known noise

Exclusions use shell patterns, not regular expressions. Quote the pattern so your shell passes the asterisk to du:

du -h --max-depth=1 --exclude='*.cache' -- /path/to/directory

This skips files and directories whose names end in .cache. An unquoted *.cache is expanded by the shell against the current working directory before du runs, which can silently change the request.

For many patterns, put one pattern per line in a file you control, then pass it with -X:

du -h --max-depth=1 --exclude-from=/path/to/excludes.txt -- /path/to/directory

Keep the exclusion file beside your notes if the result needs to be reproducible. An excluded total is an estimate for that particular question, not a statement about all space used.

Safe scripting and failure recovery

Names containing spaces are handled by du, but names containing newlines make line-oriented output difficult to consume. For a NUL-delimited list of paths, use --files0-from:

find /path/to/directory -type f -print0 |
  du --files0-from=- --null

Do not pipe that output to a tool expecting ordinary newline records. If a scan reports "Permission denied", the measurement may be incomplete. First narrow the path or inspect the permissions. If policy permits, repeat with sudo du ... and treat that as a separate privileged measurement.

du only reads metadata and file contents needed by the filesystem walk. It does not delete, compress or repair anything, so there is no state change to undo. The recovery step for a misleading result is to rerun with the relevant boundary: -x for mount points, -P for link safety, or without an exclusion for a complete named-tree estimate.

Done means