If you run ls -lh and wonder why every directory shows 4.0K, you are looking at the directory entry itself, not what lives inside the tree. du reads what is underneath.
Why ls cannot sort directories by size
ls reads directory metadata, so ls -S sorts regular files correctly while directories stay at their inode size of 4.0K.
Use du (disk usage) when you want the sum of everything under a path. It walks the tree, adds block usage, and prints one line per directory, which is exactly what sort needs to rank.
- Use du when you need combined size, including subdirectories
- Use ls when you need name, time, or permission sorting without size
Meet du and its sizing flags
I verified these on GNU coreutils 9.4, which is what Ubuntu 24.04, Debian 12, and Fedora 40 ship today. The same flags exist on older 8.x releases, only the help text differs.
du --version
# du (GNU coreutils) 9.4
man du | grep -A2 "max-depth"
The flags you will actually use for a sorted listing are few, so keep them together rather than rediscovering them each time.
| Flag | What it does | When you need it |
|---|---|---|
| -h, –human-readable | Prints K, M, G so you can read the output without math | Always for interactive use |
| -s, –summarize | Shows only the total for each argument, not every subdirectory | When you pass */ or explicit paths |
| -d N, –max-depth=N | Limits recursion to N levels (0 is the argument itself) | -d 1 for immediate children only |
| -a, –all | Includes individual files, not just directories | To find the biggest files inside a tree |
| –apparent-size | Shows logical file size instead of on-disk block usage | When sparse files or block rounding confuses you |
| –block-size=1M, -B 1M | Forces a single unit for stable numeric sorting | When sort -h is unavailable |
| -x, –one-file-system | Does not cross mount points | On hosts with /proc, /snap, or network mounts |
-d and –max-depth are the same option, so both forms print the same result. I use the short flag when typing and the long flag in scripts because that reads clearly when you revisit the pipeline.
List immediate directories with human sizes
Start with the simplest useful rank. This prints every immediate child of the current directory plus the total for the current directory itself, all in human units.
du -h --max-depth=1 .

On my demo tree with 100K, 250K and 500K subdirectories, the output was unsorted disk order: 104K for alpha, 560K for beta, 256K for gamma, and 924K for the parent. The numbers are slightly larger than the raw file sizes because du counts filesystem blocks, which means a 100K file occupies a few extra kilobytes on disk.
# explicit path works the same
du -h --max-depth=1 /var/log
# long form included for clarity in scripts
du -h -d 1 /home/aadesh/projects
Add 2> /dev/null when a tree contains unreadable directories, because otherwise du: cannot read directory warnings interleave with the sizes and break the sort.
Sort the listing so the biggest directory is on top
Pipe to sort -h for ascending or sort -hr for descending human-numeric order.
- sort -h understands K, M, G and arrived in coreutils 7.5
- sort -n compares only numbers and breaks on human sizes
du -h --max-depth=1 . | sort -h # smallest first
du -h --max-depth=1 . | sort -hr # largest first
LC_ALL=C du -h --max-depth=1 . | sort -hr # locale-stable when needed

I ran the descending variant on the same demo tree and got the expected rank: parent at 924K, then beta at 560K, gamma at 256K, alpha at 104K. Setting LC_ALL=C forces byte-wise sorting when a UTF-8 locale would otherwise treat human suffixes oddly, which matters on some Alpine and busybox-adjacent images.
If you are on a minimal image without sort -h, force a single unit and use numeric sort instead: du –block-size=1M . | sort -nr gives the same ranking at the cost of raw megabytes.
Show only the top N directories
Most of the time you want the biggest few, not the whole list. Combine tail to drop the parent total and head to keep N lines, and keep the sort you already have.
du -h --max-depth=1 2> /dev/null | sort -hr | tail -n +2 | head -n 10

tail -n +2 skips line one, which is the current directory total after a descending sort. head -n 10 then keeps the ten largest children. On the demo tree this left beta, gamma, alpha in that order and removed the 924K parent line entirely.
# without the parent total at all, summarize the children directly
du -sh --apparent-size ./*/ 2>/dev/null | sort -hr | head -n 10
# explicit count matters in scripts; plain head defaults to 10
du -h --max-depth=1 2> /dev/null | sort -hr | tail -n +2 | head
du -sh ./*/ avoids the parent line from the start because -s summarizes each argument. It needs a shell that expands */ and will miss dot-directories, so the du –max-depth form is the safer default when you are unsure.
Handle spaces, dot-directories, and permission noise
A pipeline that breaks on My Photos is not a pipeline you can trust. du prints names verbatim, and sort operates on the first column only, so spaces in names are safe here, but glob and null-byte handling still matters for later steps.
# include dot-directories if you need them
du -h --max-depth=1 . 2>/dev/null | sort -hr
# exclude pseudo-filesystems that inflate totals
du -h --max-depth=1 -x / 2>/dev/null | sort -hr | head
# when a tree is unreadable, silence is required for clean sorting
du -h --max-depth=1 /var/log 2>/dev/null | sort -hr | head
# for scripting without locale surprises
LC_ALL=C du -h -d 1 . 2>/dev/null | sort -hr | head -n 5
Use -x on the root filesystem because du would otherwise descend into /proc and /sys and stall. Use redirection rather than ignoring stderr when the output feeds sort, because warnings are not sizes.
- Stay on one filesystem with -x on /
- Redirect permission warnings to /dev/null so sort sees only sizes
Find the largest directories anywhere in a tree
When the biggest consumer is nested two or three levels down, immediate children hide it. Let du list all files and directories, then filter to directories only.
# largest files and directories together
du -ah . 2>/dev/null | sort -hr | head -n 20
# directories only, at any depth
du -h . 2>/dev/null | sort -hr | head -n 20
# alternative: let find enumerate directories, then size each one
find . -maxdepth 3 -type d -exec du -sh {} + 2>/dev/null | sort -hr | head -n 20
# threshold: only show entries over 100M
du -h --threshold=100M . 2>/dev/null | sort -hr | head
On the demo tree, du -ah . | sort -hr | head surfaced the 500K file inside beta before its sibling directories, which helps when a single log or cache file dominates. find -type d is slower but honest about hidden empties that du still reports with 4.0K.
–threshold=SIZE is available since coreutils 8.31 and avoids the grep you would otherwise add to filter small entries. It filters before sorting, which keeps large trees fast.
Make the ranking reusable with an alias or function
Typing the same five-stage pipeline invites a typo. Put a small function in ~/.bashrc or ~/.zshrc so you get tab completion and a count argument.
| Argument | Default | Effect |
|---|---|---|
| dir | . | Directory to rank |
| n | 10 | How many lines to keep |
# put this in ~/.bashrc or ~/.zshrc, then run: source ~/.bashrc
dusort() {
local dir="${1:-.}"
local n="${2:-10}"
du -h --max-depth=1 "$dir" 2>/dev/null | sort -hr | tail -n +2 | head -n "$n"
}
# usage
dusort # top 10 in .
dusort /var/log # top 10 in /var/log
dusort /home 20 # top 20 in /home
A shell alias works too, but it cannot take a directory argument cleanly. The function above handles spaces because “$dir” is quoted, and it defaults to the current directory when you call it bare.
# simple alias when you always want the same 10
alias dush='du -h --max-depth=1 2> /dev/null | sort -hr | tail -n +2 | head -n 10'
When du is not the right lens
du counts blocks, so a 1-byte sparse file can report 0, and a file with holes reports less than ls -lh shows. Add –apparent-size when you need logical size parity with ls, and keep the default when you need true disk usage.
# logical size vs disk usage on the same tree
du -sh --apparent-size demo/* | sort -hr
du -sh demo/* | sort -hr
# sparse-file check
du -h --apparent-size sparse.img
du -h sparse.img
For an interactive exploration, ncdu beats any pipeline. It scans once and lets you navigate with arrow keys, delete, and re-scan without rebuilding the sort.
sudo apt update && sudo apt install -y ncdu # Debian/Ubuntu
sudo dnf install -y ncdu # Fedora
ncdu --color dark -x /home # stay on one filesystem
Modern replacements like dust and duf add charts and mount-aware views, but they are not preinstalled on servers. Learn the pipeline first, because it exists everywhere you will ssh into.
Quick reference for your terminal
Copy the line that matches your intent. Each one is a complete command, not a fragment, and each was run against GNU coreutils 9.4 for this article.
| Goal | Pipeline |
|---|---|
| Largest immediate children | du -h -d 1 | sort -hr |
| Top 10 without parent | du -h -d 1 | sort -hr | tail -n +2 | head |
du -h --max-depth=1 . | sort -hr # rank immediate subdirs, largest first
du -h --max-depth=1 2> /dev/null | sort -hr | tail -n +2 | head -n 10 # top 10 without parent total
du -sh ./*/ 2>/dev/null | sort -hr | head -n 10 # same, via explicit args
du -ah . 2>/dev/null | sort -hr | head -n 20 # biggest files and dirs anywhere
du -h . 2>/dev/null | sort -hr | head -n 20 # directories only, any depth
du -h --threshold=100M . 2>/dev/null | sort -hr | head # only entries over 100M
LC_ALL=C du -h -d 1 . 2>/dev/null | sort -hr | head -n 5 # locale-stable pipeline
Summary
du -h –max-depth=1 lists immediate directories with human sizes, and sort -hr turns that list into a rank where the biggest entry is on top. Add tail -n +2 | head to keep the top N without the parent total, and wrap the whole pipeline in a small shell function so you can run dusort /var/log 20 whenever disk pressure returns.
Why does ls -lh show every directory as 4.0K?
ls reads the directory inode, not the files inside it. Use du -h –max-depth=1 to sum the contents, then pipe to sort -hr to rank by size.
What is the difference between du -h –max-depth=1 and du -sh */?
Both show immediate children, but –max-depth=1 walks from one argument and includes hidden children when combined with .* handling, while du -sh */ expands via the shell and misses dot-directories. –max-depth is the safer default for scripts.
How do I list only the ten largest directories?
Run du -h –max-depth=1 2> /dev/null | sort -hr | tail -n +2 | head -n 10. tail -n +2 drops the parent total that sorts to the top, and head keeps the next ten.
Why does du show a different size than ls?
du counts allocated blocks by default, while ls shows logical file length. Add –apparent-size to du when you want parity with ls. The default block count is what actually occupies disk.
