ByteScope

df

read-only

Reports free and used space per mounted filesystem.

Command
adb shell df
Commands
Files and storage
Risk
read-only

Syntax

df [-k] [-h] [-H] [-P] [-i] [<path>]

Flags and arguments

TokenMeaning
-k1024-byte blocks, and the flag that pins the header to a label you can rely on.
-hHuman-readable, base 1024.
-HHuman-readable, base 1000.
-PPOSIX output: 512-byte blocks, and the percentage column is named Capacity.
-iCount inodes instead of bytes. The columns then hold inode counts.
-t <type>Restrict the table to filesystems of one type. Worth having on Android, where an unfiltered listing buries the two partitions you care about under dozens of `tmpfs` and overlay mounts.
-aInclude the pseudo-filesystems that are hidden by default. Reach for it when a mount you expected is simply missing from the ordinary listing, before concluding it was never mounted.

Examples

  • df -k /data

    Free space on the userdata partition, in KiB.

  • df -k

    Every mounted filesystem.

  • df -h /data

    Quick to read by eye; not a number to compute with.

  • df -k /data /sdcard

    The two rows that matter on a phone, in one call, with the unit declared once in the shared header.

  • df -k -a

    Everything, pseudo-filesystems included, for when a mount point you expected does not appear in the default table.

Reading the output

Sample output

Filesystem     1K-blocks      Used Available Use% Mounted on
/dev/block/dm-9 111222333  98765432  12345678  89% /data

The header line *is* the unit declaration, and it is the only place the unit appears — so read the columns through it rather than by position. `1K-blocks` means 1024 bytes each, `512-blocks` means 512, `Size` means the value was humanised, and `Inodes` means the numbers are not bytes at all.

Output fields

FieldMeaning
1K-blocksTotal size in 1024-byte blocks. `-k` and the default.
512-blocksTotal size in 512-byte blocks. What `-P` produces.
Size / AvailHumanised values, from `-h` or `-H`. Three significant digits.
Use% / CapacityThe percentage used. The label depends on the flags.
Mounted onThe mount point. Two words in the header, one column in the rows.
-A filesystem that could not be measured. Not zero.
IFree / IUse%The inode columns `-i` substitutes for `Available` and `Use%`. A partition can be full on inodes while it still has bytes going spare, and only these two columns show it.
FilesystemThe backing device, or the source for a bind or pseudo mount. On a modern phone this is usually a device-mapper name like `/dev/block/dm-9` rather than a partition you would recognise by sight.

What access it needs

Read-only and unprivileged: no root, no `adb root`, nothing to enable first. `df` reads the mount table and asks the kernel for each filesystem's block counts, so the numbers describe the filesystem itself rather than anything stored inside it, and no directory has to be readable for a row to be right. That distinction is worth holding on to, because `du` on the same phone will refuse half of what it walks: a `/data` row from `df` is trustworthy even while `du /data` is a wall of `Permission denied`. Passing a path changes only which rows get printed — the argument is examined to find which device it lives on, and an argument the shell cannot examine is reported and skipped rather than guessed at. There is no write form here at all. Freeing space is `pm trim-caches` or a deletion, each with its own page and its own confirmation, and neither of them is reachable by mistyping a flag on this one. The Android Storage Analyzer takes its `/data` figure from this command for the same reason it is on this page: it is the one storage number on the phone that does not depend on what the shell happens to be allowed to open.

Failure messages, decoded

MessageMeaning
df: '/mnt/media_rw/0000-1111': No such file or directoryThat path argument could not be examined, so no row is printed for it while the other arguments still are. Checked against `toys/posix/df.c` on the `android11-release` through `android15-release` branches, the path really is wrapped in single quotes here — which is the reliable way to tell this line apart from a table row that happens to contain similar words, since a real row never quotes anything.
df: '/data/data/com.example': Permission deniedA path argument has to be stat'ed before `df` can work out which filesystem it sits on, and app-private storage refuses that to the shell. This is the one way a `df` question fails for a permission reason even though the block counts themselves need none: that argument gets no row, the other arguments still do. Ask about the mount point instead — `df /data` answers the same question and needs nothing.
df: Unknown option 'z' (see "df --help")Toybox does not know that letter, and nothing was measured. What it quotes is whatever remained of the argument after the dash at the moment it gave up, so `-kz` is reported as `z` rather than as the whole word — read it as "the first letter I could not place", not as the argument you typed.

Version and vendor notes

  • `-h` and `-H` are indistinguishable in the output: both print the same column labels and the same unit letters, and only the kilobyte suffix reveals its case. A humanised reading cannot tell you which base produced it, so ask for blocks when it matters.
  • The shell merges standard error into the output, so a line like `df: /mnt/media_rw/0000-1111: No such file or directory` arrives in the middle of the table and has enough words in it to look like a row. A real row has numbers where the header promised numbers.
  • On Android 5.1 and earlier `df` is a different implementation with a different header and no separate mount-point column, and it does not understand `-k` — it treats the flag as a path.
  • One flag's grouping genuinely changed between releases: `-k` sat inside the `[-HPkh]` group up to the toybox in Android 11 and left it in Android 12. Note what that kind of group does — toybox switches the earlier flag back off rather than refusing anything — so on the older build `df -P -k` silently drops `-P` and prints a plain 1024-byte-block table, and from Android 12 both flags apply. Neither build says a word about it, which is why a script that spans the boundary should send `-k` on its own.
  • Reading the checked source, the unmeasurable-path line quotes the path in single quotes. Nothing here needs root, and `adb root` would not add a single row: the mount table is world-readable and asking a mount point for its block counts does not require the right to walk inside it.

We have a UI for this

Android Storage Analyzer runs this command over USB and parses what comes back — a Chromium browser, no SDK, nothing uploaded.

Open Android Storage Analyzer

Related commands

Commands that turn up in the same session as df.

CommandWhat it does
duReports how much disk space a directory tree occupies.
dumpsys diskstatsFree space per partition, a disk write latency check, and a cached per-app breakdown of what is using storage.
pm trim-cachesAsks the system to drop cached files until the given amount of free space exists.

More Files and storage commands

CommandWhat it does
findWalks a directory tree and prints what matches — big files, empty folders, leftovers.
rmDeletes a file or a directory tree on the device.

Checked against AOSP, toybox and kernel sources while writing the parsers behind our Web ADB tools. Where a behaviour genuinely varies by Android version or manufacturer, this page says so instead of giving a number that would be wrong on half the phones out there.

Frequently asked questions

How do I run an adb shell command?

Switch on developer options and USB debugging on the phone, plug it in, then run adb shell <command> from a machine with platform-tools installed — or run adb shell on its own to get an interactive prompt and type the command without the prefix. If the command produces binary output, such as a screenshot, use adb exec-out instead of adb shell: the plain shell service attaches a terminal, which rewrites newline bytes and corrupts the data.

Do adb shell commands need root?

Not the ones on this reference. They run as the shell user, which can manage packages for its own Android user, read most dumpsys services, write the settings provider, inject input and read shared storage. Root is only needed for things this reference deliberately avoids — reading another app's private data (except through run-as on a debuggable build), writing system partitions, or reading /data/anr directly.

Why does the output look different on my phone?

Either the Android version or the manufacturer. Some flags and fields arrived in a specific release, and those are noted per command. The rest is OEM patching, which hits dumpsys hardest: a dump is debug output whose text is whatever the service author last printed, and manufacturers change it and backport their changes. Read fields by name rather than by position, and treat an unfamiliar shape as a difference rather than as a failure.

Can I run this from a browser instead of a terminal?

For many commands, yes — the Web ADB tools on this site talk to a phone over WebUSB from a Chromium-based desktop browser, with no SDK installed and nothing uploaded. Each page links to the tool that runs its command, when one exists. Two limits are worth knowing: only one program can hold the ADB interface at a time, so a desktop adb server has to be stopped first, and adb forward and adb reverse cannot work in a browser at all.

Would rather click than type? The Web ADB Toolkit runs commands like this from a browser over USB. Or browse every command.