Basic Syntax
. Tests combine left to right; the default action is . Always quote glob patterns so the shell does not expand them first.
| Command | What it does |
|---|---|
| List everything under the current directory, recursively | |
| Files named *.log under /var/log | |
| Case-insensitive name match | |
| Combine tests: files only, matching name | |
| Search several trees at once | |
| Hide Permission denied noise |
Find by Name
| Command | What it does |
|---|---|
| Exact name | |
| Glob on the file name (basename only) | |
| Ignore case | |
| Glob against the whole path | |
| Negate a test | |
| Either name ( is OR) | |
| Regex against the whole path, unlike -name | |
| Saner regex dialect (GNU) | |
| Hidden files, at any depth | |
| Skip hidden files and directories entirely | |
| Broken: the shell expands the glob before find sees it |
The last row is the mistake everyone makes once. Unquoted, becomes whatever matches in the current directory, so gets a real file name (or an error about too many arguments) instead of a pattern. Quote it: .
Find by Type
| Command | What it does |
|---|---|
| Regular files | |
| Directories | |
| Symlinks | |
| Files or symlinks ( is OR) | |
| Empty files and directories | |
| Empty directories only |
Excluding Directories
| Command | What it does |
|---|---|
| Filter results (still descends, simple) | |
| Skip the directory entirely (fast) | |
| Prune several at once | |
| Or just do not go deep |
With , keep the at the end or the pruned directories themselves show up in the output.
Find by Time
find counts age in whole 24-hour blocks: is "modified within 7 days", is "older than 7 days" (effectively 8 days and up), and is "exactly 7 days old", which is rarely what you want.
| Command | What it does |
|---|---|
| Modified in the last 24 hours | |
| Older than 30 days | |
| Modified in the last hour | |
| Untouched for more than 15 minutes | |
| Modified more recently than ref.txt | |
| Modified since a date | |
| Modified today, counting from midnight (GNU) |
Find by Size
Always give a unit: without one, the number means 512-byte blocks.
| Command | What it does |
|---|---|
| Larger than 100 MB | |
| Larger than 1 GB | |
| Smaller than 10 KB | |
| Between 1 and 5 MB | |
| Zero-byte files |
Running Commands with -exec
stands for the matched file. runs the command once per file; batches many files per run and is much faster.
| Command | What it does |
|---|---|
| One command per file | |
| Batched: one command, many files | |
| Ask before each one | |
| Search inside the matches | |
| Run in each file's own directory | |
| Feed the results to another pipeline |
requires once, at the end. If the command needs the name twice (), use .
Search File Contents
find only ever matches metadata. When the match has to be inside the file, that is grep; find's job is narrowing the set grep has to read.
| Command | What it does |
|---|---|
| grep already recurses, so no find needed here | |
| Content search inside a name-filtered set | |
| Print only the names of files that match | |
| Same, safe with spaces in names | |
| Only search what changed today | |
| List text files, skip binaries |
Deleting Files
Order matters: acts on everything matched so far, so it goes last. deletes everything under because the tests come too late.
| Command | What it does |
|---|---|
| Dry run first: see exactly what will go | |
| Then swap -print for -delete | |
| Cleanup older than 30 days | |
| Remove empty directories | |
| Alternative with visible output |
implies depth-first order and refuses non-empty directories, so it cannot take out a whole tree by accident the way can.
Pairing with xargs
and pass names separated by NUL bytes, so spaces and newlines in file names cannot break the pipeline.
| Command | What it does |
|---|---|
| Content search over the matched set | |
| Delete via xargs | |
| Place each name mid-command | |
| Four jobs in parallel | |
| Count matches (fine without -print0) |
covers most of what xargs does; xargs earns its keep for parallelism and mid-command substitution.
Permissions and Ownership
Modes pair naturally with the chmod cheat sheet and the chmod calculator.
| Command | What it does |
|---|---|
| Mode is exactly 644 | |
| Owner-writable, whatever else is set | |
| World-writable (audit) | |
| All setuid binaries (audit) | |
| Owned by alice | |
| Group www-data | |
| Orphaned files (deleted owner) | |
| Fix stragglers |
Depth and Filesystems
GNU find wants these before the other tests.
| Command | What it does |
|---|---|
| This directory only, no recursion | |
| Two levels down at most | |
| Skip the starting directory itself | |
| Stay on one filesystem (skip mounts) | |
| Follow symlinks; this spelling finds broken ones |
Permission Denied Errors
Those messages go to stderr, so they never reach a pipe or a redirected results file. They only clutter the screen.
| Command | What it does |
|---|---|
| Discard every error, including the real ones | |
| Keep the errors, just not on screen | |
| Skip unreadable files as a test (GNU) | |
| Do not even descend into unreadable directories | |
| Read everything, so there is nothing to hide | |
| Narrow the search instead of silencing it |
find still exits with status 1 after a permission error, so prints nothing even though the output looked fine.
Output Format and Sorting
is GNU only. On macOS use the or variants instead.
| Command | What it does |
|---|---|
| Newest files first | |
| A readable date next to each path | |
| Biggest files first, in bytes | |
| Base name only, no directory part | |
| Sort by time without -printf (one batch) | |
| Count the matches |
Find Where a Command Lives
Looking for an executable is a different job. The shell already knows the answer, so asking find is the slow way round.
| Command | What it does |
|---|---|
| Path the shell would actually run (POSIX, built in) | |
| Every candidate: alias, function, and each binary on PATH | |
| The familiar one, but blind to aliases and functions | |
| Binary, source, and man page locations | |
| Which Debian/Ubuntu package installed it | |
| Same on RHEL, Fedora and friends | |
| Last resort when it is not on PATH |
find vs locate vs grep vs fd
| Tool | When to use it |
|---|---|
| Live, exact, everywhere; all the tests on this page | |
| Instant name lookup from an index; refreshes it (missing files = stale index) | |
| Matches file contents, not names; see the grep cheat sheet | |
| Modern alternative: fast, regex by default, honours .gitignore, skips hidden dirs |
Real Recipes
| Command | What it does |
|---|---|
| 20 biggest files on the disk | |
| What changed in the last 24 hours | |
| Log cleanup (dry-run with -print first) | |
| Changed within a date range | |
| Web dirs to 755 (then files to 644 with -type f) | |
| Leftover editor and patch junk | |
| Which subdirectory is eating the space |
Gotchas
- Unquoted patterns break silently: works until the shell expands the glob in the current directory first. Always quote: .
- does not mean "older than yesterday": ages round down to whole days, so it matches 2 days and older.
- Test order is left to right: put cheap, selective tests (, ) before expensive ones, and absolutely last.
- macOS ships BSD find: and are missing, and it wants a path argument (, not ); gets GNU find as .
- output is not safe for shell loops when names contain spaces; use or .
- Searching includes /proc and network mounts; add or start from a narrower directory.
- When says the disk is full but cannot account for the space, a deleted file is still held open by a process. That is an lsof job, not a find one.