find walks a directory tree and matches files against expressions: name, type, size, time, owner, permission, or anything you can compose. The expression language is its own micro-DSL with order-sensitive operators, NUL-safe output via -print0, and a handful of macOS BSD versus GNU divergences that catch out scripts shipped between platforms. This page is the reference I keep open while writing batch-rename, cleanup, and pre-deploy scripts.
What does the find command do?
find recursively searches a directory tree and prints the paths of files (and directories) that match an expression. It is the canonical tool for "find every file under X that matches Y" on Unix systems. Common uses: locate files by name (-name, -iname), filter by type (-type f, -type d, -type l), filter by size (-size +100M) or modification time (-mtime -7), then act on the results with -exec, -execdir, -delete, or by piping -print0 into xargs -0. The expression is parsed left to right; tests like -type f filter, while actions like -print or -delete operate on the matches. macOS ships BSD find, which differs from GNU find in regex defaults, the absence of -regextype, and slightly different -mtime rounding. On Windows, PowerShell's Get-ChildItem covers most of the same ground with different syntax.
Time and size reference
Keep these two tables in view while you set the inputs below. find measures time in days (-mtime) or minutes (-mmin), and size with a one-letter unit suffix.
Time filters:
| Want | Expression | Notes |
|---|---|---|
| Last N minutes | -mmin -N | Minute resolution |
| Last hour | -mmin -60 | |
| Last 24 hours | -mtime -1 | Same as -mtime 0 |
| Last N days | -mtime -N | - means "less than N ago" |
| Older than N days | -mtime +N | + means "more than N ago" |
| Exactly N days old | -mtime N | The 24-hour slice N days back (rarely what you want) |
| Newer than a file | -newer FILE | Exact-timestamp comparison |
Size units (-size suffix):
| Suffix | Unit | Example | Bytes |
|---|---|---|---|
c | bytes | -size +500c | 500 |
k | kibibytes | -size +10k | 10,240 |
M | mebibytes | -size +100M | 104,857,600 |
G | gibibytes | -size +1G | 1,073,741,824 |
+ before the value means "larger than", - means "smaller than", no sign means "exactly" (rounded up to the next whole unit).
Set your OS, search path, name pattern, time window, and size threshold. Every command below rewrites itself with your values. The time dropdown shows the find expression each option maps to.
Find by task (jump table)
Skim by intent and jump straight to the section. Every command updates with the values you set above.
| I want to... | Section |
|---|---|
| Find files by name (case-sensitive or not) | Find files by name |
| Find files by extension (one or many) | Find files by extension |
| Find only files, only directories, or only symlinks | Find files by type |
| Find empty directories or empty files | Find empty directories |
| Find files larger or smaller than a threshold | Find files by size + deep dive |
| Find the largest files on disk | Find largest files on disk |
| Find files modified in the last N days | Find files modified in the last N days + deep dive |
| Find stale files not modified in N days | Find files not modified since |
| Find files owned by a user or with specific permissions | Find files by owner or permission + deep dive |
| Exclude a directory from the search | Exclude a directory with -prune |
| Match files with a regular expression | find -regex vs -name |
| Run a command on every matched file | Run a command on every match + -exec vs xargs |
| Run find operations in parallel | find with xargs -P |
| Safely pipe find output into another command | Pipe find safely into another command |
| Find files containing a specific string of text | Find files containing text (find + grep) |
| Delete files matched by find safely | Find and delete files |
| Archive matched files into a tarball | Archive find results with tar |
| rsync only the files find selected | rsync selective with find |
| Handle the macOS BSD vs GNU find differences | macOS BSD vs GNU find + deep dive |
| Decide between find and locate | find vs locate vs mlocate |
| Avoid the common find footguns | Common find gotchas |
Find files by name
Match every file whose name matches a pattern. This is what 80% of find invocations do.
find :search_path -name ':pattern'Case-insensitive match (matches .log, .LOG, .Log all the same):
find :search_path -iname ':pattern'PowerShell's -Filter is case-insensitive by default, matching Windows' filesystem behavior. On Linux/macOS, -name is case-sensitive (-iname is the insensitive variant).
Limit how deep find recurses (avoid running away into a huge tree):
find :search_path -maxdepth 2 -name ':pattern'-maxdepth N is a GNU extension that BSD find also supports, despite being non-POSIX. Both macOS and Linux accept it.
Find files by type
| Test | Matches |
|---|---|
-type f | Regular files |
-type d | Directories |
-type l | Symbolic links |
-type s | Sockets |
-type p | Named pipes (FIFOs) |
-type b | Block devices |
-type c | Character devices |
find :search_path -type f -name ':pattern'Find empty files or empty directories:
find :search_path -type f -emptyFind files by size
Set the Size value and Size unit in the panel above (the unit table is in Time and size reference). + means larger than, - means smaller than.
find :search_path -type f -size +:{size_value}:{size_unit}For "smaller than", flip the + to -: find :search_path -type f -size -:{size_value}:{size_unit}.
Find the biggest log files on disk, sorted largest first:
find :search_path -type f -name ':pattern' -printf '%s %p\n' | sort -rn | head -20-printf is GNU only. On BSD use -exec stat with the BSD stat format flags. That difference is one of the most common scripts-fail-on-macOS surprises.
Find files modified in the last N days
Pick a window from the Modified within dropdown above. The dropdown shows the find expression each option maps to (the time reference table explains the syntax).
find :search_path -type f :time_filterTo find files older than a window and delete them (typical cleanup script), flip the leading - in the expression to +. The full -mtime and -mmin reference, including the off-by-one rounding rule, is in Find files modified in the last 7 days:
find :search_path -type f -mtime +30 -deleteSubtle macOS difference: BSD find -mtime -N rounds the time differently than GNU. GNU rounds the file's age down to 24-hour units; BSD treats fractional days differently in some edge cases. For scripts that need exact behavior, use -newerXY (BSD) or -mmin (both) with explicit minutes.
Find files by owner or permission
find :search_path -type f -perm 0644Find anything world-writable (security hygiene check):
find :search_path -xdev -type f -perm -o+w 2>/dev/nullFind files owned by a specific user:
find :search_path -user www-data -type fRun a command on every match
-exec runs a command for each match. The {} placeholder is replaced with the path; the command must end with \; (escaped semicolon).
find :search_path -type f -name ':pattern' -exec grep -l 'ERROR' {} \;Replace \; with + to batch arguments into fewer command invocations (much faster on large trees):
find :search_path -type f -name ':pattern' -exec grep -l 'ERROR' {} +-execdir runs the command in the directory of the matched file. Safer when paths have spaces or weird characters because the {} is just the basename:
find :search_path -type f -name ':pattern' -execdir rm {} \;-delete is the shortest form. Use with extreme care: there is no confirmation, no dry run, and order matters (always test with -print first):
find :search_path -type f -name ':pattern' -deleteA safe pattern for any destructive find: write it with -print first, eyeball the list, then change -print to -delete.
Pipe find safely into another command
find ... | xargs command looks innocent but breaks the moment a filename contains a space, newline, or quote. The fix is NUL-delimited output: -print0 from find, -0 on xargs.
find :search_path -type f -name ':pattern' -print0 | xargs -0 gzipWhy this matters: filenames on Unix can contain literally any byte except / and NUL. -print separates with newlines, but newlines are valid in filenames. The only safe separator is NUL (\0).
This pattern is the safe equivalent of find ... -exec cmd {} +, useful when you need shell features (piping, redirection) that -exec cannot give you. It pairs with the grep cheat sheet for the most common find-then-grep pipelines.
macOS BSD vs GNU find
The divergences that matter, in order of how often they bite.
| Feature | GNU find | BSD find (macOS default) |
|---|---|---|
-regextype | Supported (-regextype posix-extended, etc.) | Not supported |
-regex default flavor | POSIX BRE (Emacs flavor) | Basic POSIX |
-printf | Supported (rich format strings) | Not supported; use -exec stat |
-mtime fractional rounding | Floors to 24h units | Different rounding edge case |
-iregex | Supported | Supported, but different default flavor |
-not operator | Supported | Supported |
-delete order | Same | Same |
GNU long options (--help, etc.) | Supported | Not supported on most options |
The single most common cross-platform script failure is -printf on macOS. There is no clean BSD equivalent; either guard with if command -v gfind, or install GNU find via Homebrew (brew install findutils) and use gfind explicitly.
brew install findutils
alias find='gfind'
alias xargs='gxargs'If your shell scripts need to run on both Linux and macOS, stick to the POSIX-ish intersection: -name, -iname, -type, -size, -mtime, -mmin, -perm, -newer, -print, -print0, -exec, -execdir, -delete. Everything outside that set risks BSD/GNU drift.
Common find gotchas
1. Forgetting to quote the pattern. find . -name *.log lets the shell expand *.log first. If a matching file exists in the current directory, that gets passed as the pattern; if none does, you get a confusing "paths must precede expression" error. Always quote: find . -name '*.log'.
2. -print is unsafe in pipelines. Filenames with spaces or newlines break xargs by default. Use -print0 | xargs -0 for any pipeline that handles arbitrary paths.
3. The -exec ... {} \; semicolon is shell metacharacter. Always escape with backslash or quote: \; or ';'. Without it, the shell ends the find command at the semicolon.
4. -exec ... {} + versus -exec ... {} \;. The + form batches arguments into a single command invocation (fast). The \; form forks once per match (slow on large trees). Use + unless the command can only accept one path at a time.
5. -delete happens depth-first but order can surprise you. If you want to delete files and then empty directories, find . -depth -type d -empty -delete after the file delete pass. The -depth flag forces children before parents.
6. -maxdepth and -mindepth must come BEFORE other tests. Putting them after -name or -type is technically legal but evaluated left to right, which can produce surprising results. Place depth restrictions first.
7. -name matches the basename only, not the path. find . -name 'src/*.ts' will never match because the slash makes it a path, not a basename. Use -path '*/src/*.ts' instead.
8. find / with no filters scans the whole filesystem. Including network mounts, /proc, /sys. Always scope with a real start path or add -xdev to stay on one filesystem.
What to do next
Deep dives by task (each one is the dedicated reference for a specific find use case):
- Find files modified in the last N days: the
-mtimeand-mminreference, with the off-by-one rule - Find files not modified since N days: the inverse
+Ndirection, for stale-file detection - Find files larger than a size: the
-sizereference for thresholds - Find the largest files on disk: the ranked "what is eating my disk" workflow
- Find and delete files safely: the
-deleteplaybook with the safe two-step pattern - Find empty directories and files:
-empty, and the-depthdeletion-order trap - Find files by extension: one extension or many, with the grouping-parens rule
- Find files by owner or permission:
-user,-perm, and the SUID/world-writable audits - Exclude a directory with -prune: the
-prune -o ... -printpattern explained - find -regex vs -name: when to use a regex, and the whole-path matching surprise
- find -exec vs xargs: when to use each, with the
{} +trick that beats both - Run find in parallel with xargs -P: the parallelism deep-dive
- Find files containing text (find + grep): the most common find-then-tool pipeline
- Archive find results with tar: the safe
-print0 | tar --nullpipeline - rsync only the files find selected: selective sync by time, type, or curated list
- BSD find vs GNU find: every macOS vs Linux divergence that matters
- find vs locate vs mlocate: live-walk vs database search, and when to use each
Related cheat sheets:
- grep cheat sheet: the classic pairing for find.
find ... -print0 | xargs -0 grepis one of the most common pipelines. - SSH cheat sheet: for running find against remote hosts.
- curl cheat sheet: for downloading lists of files that find generates.
- Bash for loop: when you need to loop over find output with logic that does not fit in
-exec. - Bash while loop: the safe
while IFS= read -r -d '' file; do ...; done < <(find ... -print0)pattern for processing files line by line. - How to zip multiple directories into individual files: a worked example of
find ... -print0 | xargs -0 zip. - How to increase Google Cloud VM disk size without rebooting: uses
findfor the cleanup pass before resizing. - External: GNU findutils manual, FreeBSD find(1), PowerShell Get-ChildItem.





