Finding files with find
find searches a directory tree by name, type, size, or modification time. -exec runs a command on each result; -print0 with xargs -0 handles filenames with spaces safely.
Globs only search the current directory. When you need to hunt through an entire directory tree — find every .log file under /var, every file larger than 100 MB, or every file touched in the last 24 hours — you need find. It descends recursively, filters on almost any file attribute, and can run a command on every match.
By the end of this lesson you will write find commands that filter by name, type, size, and modification time, and safely execute actions on the results.
After this lesson you can use find to search a directory tree by name (-name), type (-type), size (-size), and modification time (-mtime); execute a command on each result with -exec; and handle filenames containing spaces safely with -print0 and xargs -0.
Basic syntax: find <path> <expression>. The first argument is the root of the search; everything after it is a predicate. Without any predicate, find prints every file it finds:
find /var/log # list every file under /var/log recursively
find . # list every file under the current directoryPredicates are evaluated left to right and combined with implicit AND. Add -maxdepth N to limit recursion depth.
-name and -iname filter by filename pattern. The pattern uses shell globs but must be quoted so the shell does not expand it before find sees it:
find . -name "*.log" # files ending in .log
find /etc -name "nginx.conf" # exact name search
find . -iname "readme*" # case-insensitive; matches README.md, readme.txt-name matches only the filename, not the full path. To match against the path, use -path "*/dir/*.log".
-type filters by entry type. Common values:
| Flag | Meaning |
|---|---|
-type f | regular file |
-type d | directory |
-type l | symbolic link |
find . -type f -name "*.sh" # only files, not directories named *.sh
find /tmp -type d # list every directory under /tmpCombining -type f with -name is the most common pattern.
-size filters by file size. Suffixes: c (bytes), k (kibibytes), M (mebibytes), G (gibibytes). Use + for “greater than” and - for “less than”:
find /var/log -size +10M # files larger than 10 MiB
find . -size -1k # files smaller than 1 KiB
find /home -type f -size +100M # large files consuming space-mtime N filters by modification time in days. +N means “older than N days”; -N means “modified within the last N days”:
find . -mtime -1 # modified in the last 24 hours
find . -mtime +30 # not modified in over 30 days
find . -mmin -60 # modified in the last 60 minutes (-mmin uses minutes)-exec runs a command on each match. The {} placeholder is replaced by the matched filename. The expression must end with \; (one invocation per file) or + (batch all files into one invocation):
# Delete every .tmp file (one rm call per file)
find . -name "*.tmp" -exec rm {} \;
# Delete every .tmp file (one rm call with all files — faster)
find . -name "*.tmp" -exec rm {} +
# Print size of each file over 10M
find /var -size +10M -exec du -sh {} \;-print0 with xargs -0 is the safest alternative when filenames may contain spaces or newlines:
find . -name "*.log" -print0 | xargs -0 gzip-print0 separates results with a null byte instead of a newline; xargs -0 reads null-separated input. This survives filenames like my file.log.
Finding and compressing old logs.
You have a server with logs accumulating under /var/log/app. You want to gzip every .log file not modified in the past 7 days:
find /var/log/app -type f -name "*.log" -mtime +7 -print0 \
| xargs -0 gzip --bestBreaking it down:
-type f— only regular files, skip directories-name "*.log"— only log files (pattern quoted so the shell doesn’t expand it)-mtime +7— older than 7 days-print0 | xargs -0 gzip --best— pass all matches to gzip in one batch, null-delimited for safety
# Verify: list all gzipped files created today
find /var/log/app -name "*.gz" -mtime -1▸Why this works
BSD find on macOS differs from GNU find on Linux. Key gaps:
-mtimerounding: GNU rounds to the nearest 24-hour boundary; BSD is strictly 24 hours × N.-exec {} +(batch mode) is a GNU extension — macOS BSDfinddoes not support it. Use-print0 | xargs -0instead for cross-platform scripts.- GNU
findsupports-printffor custom output format; BSD does not. Usestatas a workaround on macOS.
If you write scripts that must run on both, test on macOS explicitly or use gfind (GNU findutils via Homebrew).
▸Common mistake
Forgetting to quote the -name pattern. If the current directory happens to contain a file matching the glob, the shell expands it before find sees the -name argument:
# Imagine the current dir has error.log
find /var/log -name *.log # shell expands to: find /var/log -name error.logNow find searches only for a file named error.log — not all .log files. This bug is intermittent and hard to spot. Always quote -name and -path arguments: find /var/log -name "*.log".
Which find command deletes all .tmp files under /tmp, safely handling filenames that contain spaces?
find <path> <predicates> traverses a directory tree and prints (or acts on) every entry that matches all predicates. Key predicates: -name (filename glob — always quote it), -type f/d/l (entry type), -size +N/-N (file size), -mtime +N/-N (modification time in days). -exec command {} \; runs the command per file; -exec command {} + batches all matches into one call (faster, but not available on BSD find). -print0 | xargs -0 is the safest pipeline when filenames may contain spaces.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.