find walks a directory tree and matches files by name, type, size, age, owner, or permissions, then prints them or runs a command on them. This find command cheat sheet covers the tests and actions that come up in real work, with copy-paste examples.
. 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
Regex against the whole path, unlike -name
Saner regex dialect (GNU)
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 .
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.
Live, exact, everywhere; all the tests on this page
Instant name lookup from an index; refreshes it (missing files = stale index)
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.
find Cheat Sheet FAQ
What is the difference between -mtime +7 and -mtime -7?
find measures age in whole 24-hour periods, rounding down. -mtime -7 matches files modified less than 7 days ago (recent files), -mtime +7 matches files whose age rounds to more than 7, which in practice means 8 days or older, and a bare -mtime 7 matches only the files that are exactly 7 whole days old, a much narrower set than people expect. So a cleanup like -mtime +30 keeps slightly more than 30 days of files, not fewer. When day granularity is too coarse, -mmin does the same thing in minutes: -mmin -60 is the last hour. For a precise cutoff date, use -newermt: -newermt 2026-09-01 matches files modified since that date.
What is the difference between -exec with \; and with +?
With \; find runs the command once per matched file: chmod 644 a, then chmod 644 b, and so on. With + it appends as many file names as fit onto one command line, like xargs: chmod 644 a b c. The + form is far faster for thousands of files because it starts a handful of processes instead of thousands. Use \; only when the command accepts a single file, or when you use {} more than once or not at the end, which + does not allow. The semicolon is escaped because it would otherwise end the shell command; + needs no escaping.
How do I find files containing a specific string in Linux?
That is grep's job, not find's: grep -rn "connection timeout" /var/log searches file contents recursively and prints matching lines with file and line number. Combine the two when you want content matching inside a name-filtered set: find . -name "*.py" -exec grep -n "TODO" {} + searches only Python files, and adding -l to grep prints just the file names. For trees with binary files or odd names, the safe pairing is find . -type f -print0 | xargs -0 grep -l "text". find alone only ever matches file metadata: name, size, age, type, permissions.
How do I exclude node_modules or .git from find?
The quick way is a filter: find . -type f -name "*.js" -not -path "*/node_modules/*" hides those results, but find still descends into every node_modules and wastes time in big trees. The fast way is -prune, which stops find from entering the directory at all: find . \( -name node_modules -o -name .git \) -prune -o -type f -name "*.js" -print. Read it as: if the name matches, prune (do not descend); otherwise apply your tests and -print. The explicit -print at the end matters, without it the pruned directories themselves appear in the output. For day-to-day source searching, fd does this by default by honouring .gitignore.
Is the find command recursive by default?
Yes. find . descends into every subdirectory with no flag needed, which is the opposite of ls and grep. You limit it rather than enable it: -maxdepth 1 searches only the directory itself, -maxdepth 2 goes one level further, and -mindepth 1 excludes the starting directory from the results, useful when acting on a directory's contents but not the directory. GNU find wants these depth options before tests like -name and warns otherwise. If a search is slow because it crosses into other filesystems like network mounts, -xdev keeps it on one filesystem.