find
read-onlyWalks a directory tree and prints what matches — big files, empty folders, leftovers.
- Command
- adb shell find
- Commands
- Files and storage
- Risk
- read-only
Syntax
find <path> [-type f|d] [-size +<N><suffix>] [-empty] [-maxdepth <n>] [-iname <pattern>] [-printf <format> | -exec stat -c <format> {} +]Flags and arguments
| Token | Meaning |
|---|---|
| -type f | d | Regular files, or directories. |
| -size +<N><suffix> | Bigger than N units. Always write the suffix: `c` bytes, `k` KiB, `m` MiB, `g` GiB — a bare number means 512-byte blocks and, on newer builds, something worse. |
| -empty | Directories with nothing in them, or empty files. |
| -maxdepth <n> | How deep to descend. |
| -iname <pattern> | Case-insensitive name match. Quote the pattern so the device shell leaves it alone. |
| -printf <format>Android 11+ | Print chosen fields per match: `%s` size in bytes, `%p` path, `%T@` modification time. |
| -name <pattern> | The case-sensitive counterpart of `-iname`. Shared storage compares names case-insensitively on modern builds, so the two agree there more often than they would on a desktop — which makes `-name` the wrong habit to carry over. |
| -print0 | Separate matches with a NUL byte instead of a newline. On shared storage that is not pedantry: a filename may legally contain a newline, and a newline-separated list turns one such file into two paths that do not exist. |
| -exec <cmd> {} + | Run one command over as many matches as fit on a line. The `+` form keeps a whole scan to a handful of processes; the `;` form starts one per match, which over a cable is slow enough to notice and racy enough to matter. |
Examples
find /sdcard -type f -size +100MEvery file over 100 MiB in shared storage.
find /sdcard -type d -emptyEmpty directories — the litter left behind by uninstalled apps.
find /sdcard/Download -maxdepth 1 -type f -iname '*.apk'Installers you downloaded and forgot.
find /sdcard -type f -size +100M -print0The same hunt, NUL-separated, so a filename carrying a newline cannot be split into two paths that lead nowhere.
find /sdcard -type f -size +100M 2>&1Matches and refusals in one stream, which is the only form where an empty answer can be believed.
Reading the output
One matching path per line by default. To get sizes in the same pass, either `-printf` on Android 11 and later or `-exec stat -c … {} +` everywhere — a second `stat` per file is both slow over a cable and racy.
Output fields
| Field | Meaning |
|---|---|
| <path> | One match per line. |
| %s | File size in bytes. |
| %p | The path. |
| %T@ | Modification time as seconds, then a *nanosecond count* rather than a decimal fraction — `…0.5` means five nanoseconds. |
| find: bad arg '-printf' | Android 10 or earlier. Fall back to `-exec stat`. |
| find: '<path>': Permission denied | A directory that was skipped, announced on stderr. Single-quoted, which a matched path never is. |
What access it needs
Unprivileged, with one caveat that matters more than the permission itself. `find` has to open every directory it descends into, and the shell's reach is exactly the supplementary group list `adbd` grants it before dropping privileges — `sdcard_r`, `sdcard_rw`, `ext_data_rw`, `ext_obb_rw` — so shared storage walks and `/data/data` does not. The trap is not the refusal but where the refusal goes: an unreadable directory is announced on **stderr** while matches keep arriving on stdout, so `find /sdcard … 2>/dev/null` converts a partial scan into something that reads as a complete one. A large-file hunt that found nothing over 100 MiB with no error lines is a very different claim from one that found nothing while forty refusals were being thrown away, and only the second stream distinguishes them. Keep the two streams, or merge them deliberately, but do not discard the errors on a command whose answer decides what gets deleted next. Nothing here writes — `find` prints paths, and every destructive step afterwards is a separate `rm` you get to read first. The Android Storage Analyzer is built on that separation: one `find` per question, sizes gathered in the same pass so nothing has to be re-stat'ed, and a delete list assembled from what actually came back rather than from a wildcard nobody expanded on screen.
Failure messages, decoded
| Message | Meaning |
|---|---|
| find: bad arg '-printf' | Android 10 and earlier: that build's `find` has no `-printf` at all, and the complaint arrives before a single path is printed, so the result is empty rather than merely missing its size column. Fall back to the `-exec stat -c … {} +` form, which produces the same three fields on every release this family supports. |
| find: '/sdcard/Android/data/com.example': Permission denied | One directory could not be opened; the rest of the walk is unaffected and keeps printing. The path is wrapped in single quotes, and that is the dependable way to tell this line apart from a match, because a matched path is always printed bare. If you are merging the streams, the quotes are your only marker. |
| find: bad -type 'x' | `-type` takes exactly one letter — `f` for a regular file, `d` for a directory — and anything else stops the command before it walks anywhere. Nothing was searched, so an empty result here means the question was never asked. |
| find: '-size' needs 1 arg | A predicate that requires a value was written without one, usually because the surrounding shell swallowed the value while expanding something. Quote the argument. As with `-type`, this happens during the syntax pass, so no directory was ever opened. |
| find: big not [+-]N | The value handed to a numeric predicate is not a number with an optional leading sign. `-size` wants `+100M`, `-100M` or a bare count, and the word it quotes back is what your shell actually delivered — which is how a glob that matched a filename ends up standing where a threshold should have been. |
Version and vendor notes
- Android's `find` is toybox, not GNU, and `-size` behaves differently: the unit multiplier is applied only when the argument ends in a digit. With an explicit suffix the comparison is against bytes directly and there is no rounding up to whole units.
- A bare `-size +100` is a bug waiting to happen. On Android 10 to 14 it means 100 times 512 bytes; on newer upstream code the internal units are negative, which makes `+N` match every file on the device.
- `m` is 1024 squared, not a million. Only a trailing `d` would switch to powers of 1000.
- Pass `-type f` explicitly. `-size` implying a regular file is newer than every shipping Android this family supports.
- Refusals leave on stderr while matches leave on stdout, so silencing stderr rewrites "I was not allowed to look there" as "there is nothing there". It is the most expensive mistake available on this page, because the step after a scan is usually a deletion chosen from its result.
- `-printf` arrived with Android 11's toybox and is still present on the `android14-release` and `android15-release` sources. Android 10 and earlier answer `find: bad arg '-printf'` and print nothing whatsoever, so treat an empty scan on an old phone as a failed command until the error stream says otherwise.
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.
Related commands
Commands that turn up in the same session as find.
| Command | What it does |
|---|---|
| du | Reports how much disk space a directory tree occupies. |
| df | Reports free and used space per mounted filesystem. |
| rm | Deletes a file or a directory tree on the device. |
| dumpsys diskstats | Free space per partition, a disk write latency check, and a cached per-app breakdown of what is using storage. |
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.