du
Report how much disk a directory tree uses
du walks a directory tree and reports how much disk each part of it uses. du -sh somedir is
the whole command most of the time: a single total, in units a person can read.
Allocation, not length
The number du prints is the space allocated to a file, not the file's length. Space is handed
out in whole blocks, usually 4096 bytes, so a 10240-byte log occupies three blocks and du
reports 12K where ls -l reports 10240.
--apparent-size asks for the length instead, and the gap between the two runs both ways. A
sparse file is the extreme case: truncate -s 100M produces a file 100M long with no blocks
behind it at all, which du reports as 0. Compression, block suballocation and filesystems that
inline small files move the number too, so du on one tree can differ between machines where
--apparent-size does not.
Reading the output
du prints a directory once it has finished walking it, deepest first, so a subdirectory appears
above its parent and the top of the tree is the last line rather than the first.
Siblings come out in directory order, which is neither alphabetical nor sorted by size. Anything
printing more than one line is worth piping through sort, and sort -h
understands the suffixes -h produces.
Finding the largest files is that pipeline written out.
-s collapses a tree to one total and --max-depth=N reports down to a chosen level, which is
usually a better starting point than -a on anything large.
What gets counted once
Two names for one file are two directory entries and one set of blocks, and du counts those
blocks for whichever name it reaches first. A tree of hard links reports the size of the data
rather than the sum of the names, unless -l asks for every name to be counted.
A symlink contributes its own tiny entry rather than whatever it points at, so a tree full of
links to somewhere else looks nearly empty. -L follows them and measures the targets.
When du and df disagree
du adds up what it can reach by walking the tree. df asks the filesystem how
many blocks are free. A file deleted while a process still holds it open belongs to neither, so df counts space
du cannot find, and it comes back when that process exits.
Sample files used on this page
Every example below was run against these files. Recreate them to follow along.
projects/ the same sample tree the find and ls pages use: nested directories, two symlinks (one broken), a hidden directory, and files of deliberately different sizes, modes and ages
projects:
total 20
drwxr-xr-x 2 user user 4096 Jun 26 10:00 .hidden
drwxr-xr-x 2 user user 4096 Jun 26 10:00 backups
drwxr-xr-x 2 user user 4096 Jun 26 10:00 empty-dir
drwxr-xr-x 2 user user 4096 Jun 26 10:00 logs
drwxr-xr-x 3 user user 4096 Jun 26 10:00 src
projects/.hidden:
total 4
-rw-r--r-- 1 user user 13 Jun 24 10:00 .env
projects/backups:
total 16
-rw------- 1 user user 5120 May 31 2025 site-2026-01-01.tar.gz
-rw-r--r-- 1 user user 5120 Jun 15 15:30 site-2026-06-01.tar.gz
projects/empty-dir:
total 0
projects/logs:
total 6156
-rw-r--r-- 1 user user 10240 Jun 21 10:00 app.log
-rw-r--r-- 1 user user 6291456 Jun 20 10:00 big.log
lrwxrwxrwx 1 user user 14 Jun 25 10:00 main-link.js -> ../src/main.js
lrwxrwxrwx 1 user user 12 Jun 25 10:00 rotated-link -> app.log.1.gz
projects/src:
total 12
-rwxrwxrwx 1 user user 8 Jun 23 10:00 main.js
-rw-r--r-- 1 user user 8 Jun 1 09:00 util.js
drwxr-xr-x 2 user user 4096 Jun 26 10:00 vendor
projects/src/vendor:
total 4
-rw-r--r-- 1 user user 7 Jun 22 10:00 lib.js
disk-image.img a sparse file: 100M long with nothing written into it. ls -ls prints the allocated size first and the length second, so the leading 0 is the whole point of it.
0 -rw-r--r-- 1 user user 104857600 Jun 26 10:00 disk-image.img
snapshots/ two names for one 2M file, made with ln. The 2 in the link-count column is what tells them apart from two separate files of the same size.
total 4096
-rw-r--r-- 2 user user 2097152 Jun 26 10:00 monday.tar
-rw-r--r-- 2 user user 2097152 Jun 26 10:00 tuesday.tar
Totals for a directory
-s asks for one summary line instead of a line per directory, and -h scales the number to a unit you can read. Together they are most of what anyone types.
Total up one directory
du -sh projects
The whole tree in one line. Without -h the number is in kibibytes, which is rarely what you wanted to know.
Show output
6.1M projects
Total up several things at once
du -sh projects snapshots disk-image.img
Each argument gets its own line, in the order given. Files are allowed as well as directories.
Show output
6.1M projects
2.1M snapshots
0 disk-image.img
Add a grand total across the arguments
du -csh projects snapshots
-c appends a total line. It is the only way to get one figure covering several directories, since du never adds up its arguments otherwise.
Show output
6.1M projects
2.1M snapshots
8.1M total
Total up the working directory
du -sh .
. covers everything below where you are, hidden entries included, which is the difference between this and a glob.
Show output
8.1M .
Get the number without the filename
du -sh projects | cut -f1
du separates the two columns with a tab, so cut with no -d picks the size off cleanly. This is the form to assign to a variable.
Show output
6.1M
Total in kibibytes, the default unit
du -s projects
With no -h and no -B, the number is in units of 1024 bytes. Machine-readable and directly comparable between runs, which -h is not.
Show output
6216 projects
Size each entry in a directory separately
du -sh projects/*
The shell expands the glob and hands du each name, so this is one total per top-level entry rather than one for the tree.
Show output
20K projects/backups
4.0K projects/empty-dir
6.1M projects/logs
20K projects/src
Make the glob include hidden entries
du -sh projects/.[!.]* projects/*
* never matches a leading dot, so the previous example silently skipped .hidden and its 8K. The extra pattern matches names starting with a dot but excludes . and ...
Show output
8.0K projects/.hidden
20K projects/backups
4.0K projects/empty-dir
6.1M projects/logs
20K projects/src
Size two named files
du -sh projects/logs/big.log projects/logs/app.log
Given files rather than directories, du reports each one's allocation and nothing else. -s changes nothing here, since a file has no tree to summarise.
Show output
6.0M projects/logs/big.log
12K projects/logs/app.log
See what an empty directory costs
du -h projects/empty-dir
A directory is itself a file holding names, so it occupies a block whether or not anything is in it. Every directory in these listings carries that 4K.
Show output
4.0K projects/empty-dir
How deep to look
By default du prints a line for every directory it walks and stays quiet about individual files. --max-depth trims the bottom of that, and -a extends it down to files.
Report every directory in the tree
du -h projects | sort -h
The plain form: one line per directory, at any depth. Piped through sort -h because du emits siblings in directory order, which is neither alphabetical nor sorted by size.
Show output
4.0K projects/empty-dir
8.0K projects/.hidden
8.0K projects/src/vendor
20K projects/backups
20K projects/src
6.1M projects
6.1M projects/logs
Stop at the first level down
du -h --max-depth=1 projects | sort -h
projects/src/vendor is gone; projects/src still counts it. This is the usual first command to run on a full disk, because it names the subtree to look in next.
Show output
4.0K projects/empty-dir
8.0K projects/.hidden
20K projects/backups
20K projects/src
6.1M projects
6.1M projects/logs
Write --max-depth the short way
du -h -d1 projects | sort -h
-d is the same option. It is a GNU extension rather than POSIX, so a script that has to run on BSD or busybox should spell out --max-depth, or not use it.
Show output
4.0K projects/empty-dir
8.0K projects/.hidden
20K projects/backups
20K projects/src
6.1M projects
6.1M projects/logs
Go one level deeper
du -h --max-depth=2 projects | sort -h
projects/src/vendor reappears at depth 2. Each extra level multiplies the lines, which is why -a on a real filesystem is usually unreadable.
Show output
4.0K projects/empty-dir
8.0K projects/.hidden
8.0K projects/src/vendor
20K projects/backups
20K projects/src
6.1M projects
6.1M projects/logs
Ask for depth zero
du -h --max-depth=0 projects
Identical to du -sh. Worth knowing when the depth is a variable in a script and 0 has to be a legal value for it.
Show output
6.1M projects
Include individual files
du -ah projects/backups | sort -h
-a reports files as well as directories. Both archives are 5120 bytes long and both take 8K, because allocation happens in whole blocks.
Show output
8.0K projects/backups/site-2026-01-01.tar.gz
8.0K projects/backups/site-2026-06-01.tar.gz
20K projects/backups
See files at every depth
du -ah projects/src | sort -h
With -a the directory lines stay, so projects/src/vendor appears both as a total and as the file inside it. The column does not add up to the tree, by design.
Show output
4.0K projects/src/main.js
4.0K projects/src/util.js
4.0K projects/src/vendor/lib.js
8.0K projects/src/vendor
20K projects/src
Count a directory without its subdirectories
du -Sh projects | sort -h
-S reports what each directory holds directly, leaving subdirectory totals out. projects drops to 4K because everything in it is a subdirectory.
Show output
4.0K projects
4.0K projects/empty-dir
8.0K projects/.hidden
8.0K projects/src/vendor
12K projects/src
20K projects/backups
6.1M projects/logs
Compare -S against the default on one directory
du -Sh projects/src && du -h projects/src
12K against 20K, and the 8K difference is vendor. Use -S to find the directory that is itself fat rather than the one containing something fat.
Show output
8.0K projects/src/vendor
12K projects/src
8.0K projects/src/vendor
20K projects/src
Ranking what is biggest
du has no sorting of its own, so every hunt for space is du piped into sort. sort -h reads the suffixes that du -h writes.
Rank everything, biggest first
du -ah projects | sort -rh | head -5
The standard disk-space hunt. Finding the largest files is the same pipeline aimed at a real filesystem.
Show output
6.1M projects/logs
6.1M projects
6.0M projects/logs/big.log
20K projects/src
20K projects/backups
Rank the top-level directories only
du -h --max-depth=1 projects | sort -rh | head -3
Fewer lines and the same answer about where to look next. On a filesystem with a large tree this is much faster to read than the -a ranking.
Show output
6.1M projects/logs
6.1M projects
20K projects/src
Show only entries above a size
du --threshold=1M -h projects | sort -h
--threshold filters inside du, so nothing under 1M reaches the pipe at all. Handy when the tree is big enough that the noise is the problem.
Show output
6.1M projects
6.1M projects/logs
Show only entries below a size
du --threshold=-1M -h projects | sort -h
A negative threshold inverts the test, which finds the thousands of small files that add up between them instead of the one large one.
Show output
4.0K projects/empty-dir
8.0K projects/.hidden
8.0K projects/src/vendor
20K projects/backups
20K projects/src
Total a set of files matching a pattern
du -ch projects/logs/*.log
The glob picks the files and -c answers how much they come to together. This is the question behind most "can I delete the logs?" investigations.
Show output
12K projects/logs/app.log
6.0M projects/logs/big.log
6.1M total
Blocks, bytes and apparent size
du counts the blocks a file occupies. --apparent-size counts the bytes it contains. The two differ in both directions, and which one you want depends on the question.
See a file's allocated size
du -h projects/logs/app.log
app.log is 10240 bytes long. Three 4K blocks are needed to hold it, so du says 12K.
Show output
12K projects/logs/app.log
See a file's length instead
du -h --apparent-size projects/logs/app.log
The same file measured the way ls -l measures it. Use this when comparing against a byte count from somewhere else, such as a Content-Length.
Show output
10K projects/logs/app.log
Get an exact byte count
du -b projects/logs/app.log
-b is --apparent-size --block-size=1, so the figure is bytes with no rounding anywhere. The one form of du output safe to do arithmetic on.
Show output
10240 projects/logs/app.log
Measure a sparse file
du -h disk-image.img
disk-image.img was made with truncate -s 100M and never written to, so the filesystem has allocated it nothing at all.
Show output
0 disk-image.img
Measure the same sparse file by length
du -h --apparent-size disk-image.img
100M of addressable file with no blocks behind it. Copying it with cp rather than cp --sparse=always is how a 0-byte file becomes a 100M one.
Show output
100M disk-image.img
Watch the gap run the other way
du -sh snapshots && du -sh --apparent-size snapshots
Here the allocation is larger than the contents: 2.1M against 2.0M, because the directory itself takes a block that its files' lengths do not include.
Show output
2.1M snapshots
2.0M snapshots
Report in kibibytes
du -sk projects
-k is the default on Debian, so this is explicit rather than different. Worth typing in a script, because POSIXLY_CORRECT and some other systems default to 512-byte blocks.
Show output
6216 projects
Report in mebibytes
du -sm projects
6216K is a little over six mebibytes, and -m reports 7: the unit is truncated to whole numbers, and du rounds up rather than down.
Show output
7 projects
Choose the unit explicitly
du -s --block-size=1M projects
--block-size takes a number or a suffix and covers anything -k and -m do not. 1M here is identical to -m.
Show output
7 projects
Keep the unit in the output
du -s --block-size=K projects
A suffix with no number prints the unit alongside the figure. Useful in a report that a person reads, awkward in one a script parses.
Show output
6216K projects
Use powers of 1000 rather than 1024
du --si -s projects
--si divides by 1000, so the same tree that -h calls 6.1M becomes 6.4M. Disk manufacturers quote capacity this way, which accounts for the missing space in a new drive.
Show output
6.4M projects
Total a tree in exact bytes
du -sb projects
The sum of every file's length, ignoring block allocation entirely. It will not match du -s on any tree containing more than a handful of files.
Show output
6311998 projects
Symlinks and hard links
A symlink and a hard link both make one file reachable by two names, and du treats them completely differently. An estimate built on the wrong one of those comes out double.
Measure a symlink
du -h projects/logs/main-link.js
main-link.js points at ../src/main.js. du reports the link itself, whose target path is short enough to live inside the inode, so it costs nothing.
Show output
0 projects/logs/main-link.js
Follow the symlink instead
du -Lh projects/logs/main-link.js
-L measures what the link points at. A tree of symlinks into somewhere else looks empty without it and full with it, and both answers are useful.
Show output
4.0K projects/logs/main-link.js
Hit a broken symlink with -L
du -Lsh projects/logs
rotated-link points at a file that no longer exists, so following it fails. du reports the error, keeps going, and exits non-zero, which will fail a script running under set -e.
Show output
du: cannot access 'projects/logs/rotated-link'
6.1M projects/logs
Total a directory of hard links
du -sh snapshots
monday.tar and tuesday.tar are two names for one 2M file. du counts its blocks once and reports 2.1M rather than 4.1M.
Show output
2.1M snapshots
Count every name separately
du -slh snapshots
-l counts a file once per link, which doubles the total here. This is the right answer when estimating an extraction that will not preserve the links.
Show output
4.1M snapshots
Spell -l out in full
du -sh --count-links snapshots
The long name for -l, and a much clearer thing to find in a script six months later.
Show output
4.1M snapshots
List the linked files individually
du -alh snapshots | sort -rh
With -a alone, only the first name du reaches is listed and the second is skipped silently. -l makes both appear, at the cost of a total that overstates the disk.
Show output
4.1M snapshots
2.0M snapshots/tuesday.tar
2.0M snapshots/monday.tar
Filtering, counting and scripting
The rest of du is about narrowing what it walks and making its output safe to hand to something else.
Skip files matching a pattern
du -sh --exclude=*.log projects/logs
The pattern is matched against the base name, not the path. Both logs are dropped and 6.1M becomes the 4K of the directory itself.
Show output
4.0K projects/logs
Exclude by extension
du -h --exclude=*.gz projects/backups
Quote the pattern (--exclude='*.gz') if the shell might expand it against the working directory first. Here it cannot, since nothing beside the tree ends in .gz.
Show output
4.0K projects/backups
Exclude a whole subdirectory
du --exclude=vendor -h projects/src
A directory name excludes the directory and everything under it, which is how node_modules or .git gets left out of an estimate.
Show output
12K projects/src
Count inodes instead of blocks
du --inodes -s projects
17 files and directories. A filesystem can run out of inodes while gigabytes of space remain, and this is the tool that finds where they went.
Show output
17 projects
Find which subtree holds the most files
du --inodes -h --max-depth=1 projects | sort -h
The same shape as the size version, counting entries. -h still applies, so a directory with 12000 files reports 12K.
Show output
1 projects/empty-dir
2 projects/.hidden
3 projects/backups
5 projects/logs
5 projects/src
17 projects
Show when a directory last changed
du --time -h projects/backups
--time inserts the newest mtime found anywhere in the tree as a middle column. It answers whether a large directory is still in use before you delete it.
Show output
20K 2026-06-26 10:00 projects/backups
Choose the time format
du --time --time-style=+%Y-%m-%d -h projects/backups
--time-style takes a date-style format string after a +, along with the names full-iso, long-iso and iso.
Show output
20K 2026-06-26 projects/backups
Stay on one filesystem
du -x -sh projects
-x refuses to descend into anything mounted elsewhere. Running du -sh / without it walks /proc, network shares and every attached disk.
Show output
6.1M projects
Produce output safe for any filename
du -0 -s projects/* | xargs -0 -n1 echo
-0 ends each line with a NUL instead of a newline, so a filename containing a newline cannot split a record. xargs -0 is what reads it back.
Show output
20 projects/backups
4 projects/empty-dir
6160 projects/logs
20 projects/src
Read the list of paths from stdin
printf '%s\0' projects snapshots | du --files0-from=- -sch
--files0-from reads NUL-separated paths where -0 writes them, and it is how to hand du a list from find -print0 without an argument-length limit.
Show output
6.1M projects
2.1M snapshots
8.1M total
Ask for something that is not there
du -sh projects/nope
du prints the reason on stderr and exits 1. A missing path among several does not stop the others being reported, so check the exit status rather than the output.
Show output
du: cannot access 'projects/nope': No such file or directory