TechEarl

find Command Cheat Sheet: Search, Filter, and -exec Examples

A scannable find reference: search by name, size, time, type, perms; safe pipelines with -print0 and xargs -0; -exec and -execdir; plus the macOS BSD vs GNU find divergences and Windows PowerShell equivalents.

Ishan KarunaratneIshan Karunaratne⏱️ 11 min readUpdated
find command cheat sheet: search by name, type, size, time, perms; -exec, -execdir, -delete, -print0 with xargs -0; BSD vs GNU find differences on macOS; Get-ChildItem equivalents on Windows.

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.

Try it with your own values

Jump to:

Find every file with a given name in the current tree.

bash· Linux (GNU)
find . -name '*.log'

Case-insensitive match.

bash· Linux (GNU)
find . -iname '*.log'

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 depth (don't recurse forever):

bash· Linux (GNU)
find . -maxdepth 2 -name '*.log'

-maxdepth N is a GNU extension that BSD find also supports, despite being non-POSIX. Both macOS and Linux are happy with it.

Filter by type

TestMatches
-type fRegular files
-type dDirectories
-type lSymbolic links
-type sSockets
-type pNamed pipes (FIFOs)
-type bBlock devices
-type cCharacter devices
bash· Linux (GNU)
find /var/log -type f -name '*.gz'

Find empty files or empty directories:

bash· Linux (GNU)
find . -type f -empty

Filter by size

-size N accepts a unit suffix: c (bytes), k (KiB), M (MiB), G (GiB). Prefix with + for "greater than", - for "less than".

bash· Linux (GNU)
find . -type f -size +100M

Find the biggest log files, sorted:

bash· Linux (GNU)
find /var/log -type f -name '*.log' -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.

Filter by modification time

TestMeaning
-mtime -NModified less than N days ago
-mtime +NModified more than N days ago
-mtime NModified exactly N days ago
-mmin -NModified less than N minutes ago
-newer FILEModified more recently than FILE
-atime / -ctimeAccess time / inode change time (same units)
bash· Linux (GNU)
find . -type f -mtime -7

Files older than 30 days (typical cleanup script):

bash· Linux (GNU)
find /tmp -type f -mtime +30 -delete

Subtle 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.

Filter by permissions and owner

bash· Linux (GNU)
find . -type f -perm 0644

Find anything world-writable (security hygiene check):

bash· Linux (GNU)
find / -xdev -type f -perm -o+w 2>/dev/null

Filter by owner:

bash· Linux (GNU)
find /var/www -user www-data -type f

Acting on matches: -exec, -execdir, -delete

-exec runs a command for each match. The {} placeholder is replaced with the path; the command must end with \; (escaped semicolon).

bash· Linux (GNU)
find . -type f -name '*.log' -exec grep -l 'ERROR' {} \;

Replace \; with + to batch arguments into fewer command invocations (much faster):

bash· Linux (GNU)
find . -type f -name '*.log' -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:

bash· Linux (GNU)
find . -type f -name '*.tmp' -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):

bash· Linux (GNU)
find /tmp/cache -type f -name '*.bak' -delete

A safe pattern for any destructive find: write it with -print first, eyeball the list, then change -print to -delete.

Safe pipelines: -print0 and xargs -0

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.

bash· Linux (GNU)
find . -type f -name '*.log' -print0 | xargs -0 gzip

Why 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.

FeatureGNU findBSD find (macOS default)
-regextypeSupported (-regextype posix-extended, etc.)Not supported
-regex default flavorPOSIX BRE (Emacs flavor)Basic POSIX
-printfSupported (rich format strings)Not supported; use -exec stat
-mtime fractional roundingFloors to 24h unitsDifferent rounding edge case
-iregexSupportedSupported, but different default flavor
-not operatorSupportedSupported
-delete orderSameSame
GNU long options (--help, etc.)SupportedNot 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.

bash
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 pitfalls

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

FAQ

TagsfindCLILinuxmacOSBSDPowerShellShell Scriptingxargs
Share
Ishan Karunaratne

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years across software, Linux systems, DevOps, and infrastructure — and a more recent focus on AI. Currently Chief Technology Officer at a tech startup in the healthcare space.

Keep reading

Related posts

AWS S3 CLI cheat sheet: aws s3 cp local-to-S3, S3-to-local, S3-to-S3 cross-region; aws s3 sync incremental with --delete; --exclude and --include patterns; --storage-class STANDARD_IA / INTELLIGENT_TIERING / GLACIER; --sse AES256 and --sse aws:kms; --acl bucket-owner-full-control; --dryrun for safety; concurrency tuning with max_concurrent_requests and multipart_chunksize; the trailing-slash gotcha that ruins half of all aws s3 cp invocations.

AWS S3 cp and sync Cheat Sheet: Copy, Move, and Sync Files with the CLI

A scannable AWS S3 CLI reference: aws s3 cp, sync, mv, rm, ls; recursive uploads and downloads; --exclude / --include filters; storage classes (STANDARD_IA, GLACIER, INTELLIGENT_TIERING); SSE encryption (AES256, aws:kms); --dryrun safety; the trailing-slash gotcha; concurrency tuning via max_concurrent_requests and multipart_chunksize; cross-account profiles.