du

Report how much disk a directory tree uses

Updated 2026-08-24

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
54 outputs, collapsed by default

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

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.

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