When something goes missing, you usually know something about it: a name fragment, that it is a log, that it grew huge last week. What you do not know is the path. That is what find is for. It walks a directory tree, applies filters, and prints (or acts on) every match.
You do not need every predicate on day one. Start with a name search, then layer on type, size, and time as the tree gets noisier. The sections below walk through the commands you will reach for most often — with enough context that each one feels intentional, not magical.
Warm-up: build a small playground
Before diving into flags, give yourself a sample tree. The layout below mirrors a messy project: nested folders, mixed extensions, a couple of oversized logs, and a dependency folder you usually want to skip.
Create it once, then reuse it for every example in this article:
mkdir -p demo/{logs,src,docs,node_modules/pkg}
echo 'hello' > demo/readme.txt
echo 'notes' > demo/docs/Notes.TXT
echo 'main' > demo/src/app.js
echo 'error timeout' > demo/logs/app.log
echo 'error refused' > demo/logs/error.log
dd if=/dev/zero of=demo/logs/huge.log bs=1k count=120 status=none
echo 'dep' > demo/node_modules/pkg/index.js
touch -d '10 days ago' demo/logs/error.log
touch -d '2 days ago' demo/logs/app.log
With the tree ready, list every path under demo. This is the simplest useful form of find: a starting directory, then optional filters.
find demo
You should see directories and files together — find prints everything it visits unless you narrow it:
demo
demo/readme.txt
demo/logs
demo/logs/app.log
demo/logs/error.log
demo/logs/huge.log
demo/src
demo/src/app.js
demo/docs
demo/docs/Notes.TXT
demo/node_modules
demo/node_modules/pkg
demo/node_modules/pkg/index.js
Note: If nothing prints, that usually means the path does not exist or your filters excluded everything — not that find “failed silently.” Double-check the starting directory first.
Find by name
Most hunts start with a name pattern. -name matches the basename against a shell-style glob. Quote the pattern so the shell does not expand * before find sees it.
To list every .log file under demo:
find demo -name '*.log'
You should see the three logs:
demo/logs/app.log
demo/logs/error.log
demo/logs/huge.log
Case can wander in human-written files. -iname ignores case, so Notes.TXT shows up when you search for *.txt:
find demo -iname '*.txt'
demo/readme.txt
demo/docs/Notes.TXT
Note: -name and -iname match only the final component of the path, not parent folders. Searching for logs will not match demo/logs/app.log — use -path '*/logs/*' when the directory name is part of what you care about.
Filter by type
A name search returns files and directories when both match. When you only want regular files — or only folders — say so with -type.
List files only:
find demo -type f
List directories only:
find demo -type d
Stack type with name when you want “every JavaScript file,” not a folder that happens to be named like one:
find demo -type f -name '*.js'
demo/src/app.js
demo/node_modules/pkg/index.js
Note: Reach for -type f early in cleanup and audit commands. It keeps directories out of -delete and -exec pipelines where a folder match would be surprising or dangerous.
Size and time
Name and type answer what. Size and modification time answer which ones are the problem — oversized logs, stale caches, leftovers from last month.
-size takes a number plus a unit. + means “larger than,” - means “smaller than.” Units include k (kilobytes), M (megabytes), and c (bytes). Our huge.log is about 120 KB, so this finds it:
find demo -type f -size +100k
demo/logs/huge.log
Time filters are just as common. -mtime +N means “modified more than N days ago.” In the sample tree, error.log was touched ten days ago:
find demo -type f -mtime +7
demo/logs/error.log
For shorter windows, -mmin works in minutes. Useful when you are chasing a file that appeared during the last deploy:
find demo -type f -mmin -60
Note: Always list with find before you delete by size or age. A wrong +/- sign or an over-broad starting path can match far more than you expect. Preview, then act.
Combine predicates
By default, find ANDs the tests you write: a path must match every condition. That is usually what you want — “log files larger than 100 KB”:
find demo -type f -name '*.log' -size +100k
demo/logs/huge.log
OR needs an explicit -o, and grouping needs escaped parentheses so the shell does not treat them specially. This keeps .log files or .txt files:
find demo -type f \( -name '*.log' -o -name '*.txt' -o -iname '*.TXT' \)
Because -iname '*.TXT' already covers Notes.TXT, you can simplify to:
find demo -type f \( -name '*.log' -o -iname '*.txt' \)
Note: Quote globs and escape \( \) until the command becomes muscle memory. Most “find returned nothing” surprises come from the shell eating * or parentheses before find runs.
Act on matches
Printing paths is step one. The real work is often deleting, counting, or handing each match to another command.
-delete removes matches in place. Safer than piping to rm because it stays inside find and refuses to delete . by accident. Preview first, then delete the oversized log:
find demo -type f -name '*.log' -size +100k
find demo -type f -name '*.log' -size +100k -delete
-exec runs a command once per match when you end with \;, or batches many paths into fewer invocations when you end with +. Count lines in every remaining log:
find demo -type f -name '*.log' -exec wc -l {} +
Typical output looks like this (line counts depend on what you left in the tree):
1 demo/logs/app.log
1 demo/logs/error.log
2 total
Move matches into a quarantine folder when you are not ready to delete yet:
mkdir -p demo/quarantine
find demo/logs -type f -name 'error.log' -exec mv {} demo/quarantine/ \;
Note: Prefer -exec … + when the command accepts multiple file arguments (wc, grep, rm). Use \; when the command must run once per file (or only accepts a single path).
Skip noisy trees
Dependency folders and VCS metadata drown useful results. -prune stops find from descending into a directory — the “unusual path logic” that recursive grep cannot always express cleanly.
Skip node_modules while listing every .js file elsewhere under demo:
find demo -path '*/node_modules/*' -prune -o -type f -name '*.js' -print
demo/src/app.js
The pattern reads as: if the path is inside node_modules, prune (do not descend); otherwise, keep .js files and print them. Always add -print (or another action) on the right side of -o, or find may print the pruned directories too.
The same idea skips .git in a real repo:
find . -name .git -prune -o -type f -name '*.md' -print
Note: When text search is the goal and you only need includes/excludes, prefer recursive grep. Reach for find when metadata (size, age, type) or custom prune rules come first.
Hand off to grep
find picks the files; grep reads their contents. Combine them when you need both path filters and a text pattern.
Null-delimited output handles filenames with spaces safely. -print0 pairs with xargs -0:
find demo -type f -name '*.log' -print0 | xargs -0 grep -n ERROR
You can keep it inside find with -exec instead:
find demo -type f -name '*.log' -exec grep -Hn ERROR {} +
Note: Rule of thumb — recursive grep when you care about text and simple include/exclude rules; find when you care about where the file lives or how big/old it is, then pipe or -exec into grep for the content check.
Quick reference card
Keep this nearby until the predicates become muscle memory:
| Goal | Command |
|---|---|
| By name | find dir -name '*.log' |
| Case-insensitive name | find dir -iname '*.txt' |
| Files only | find dir -type f |
| Directories only | find dir -type d |
| Larger than 100 KB | find dir -type f -size +100k |
| Older than 7 days | find dir -type f -mtime +7 |
| Name OR name | find dir \( -name '*.log' -o -name '*.txt' \) |
| Delete matches | find dir -type f -name '*.tmp' -delete |
| Run a command | find dir -type f -exec wc -l {} + |
| Skip a folder | find dir -path '*/node_modules/*' -prune -o -type f -print |
| Pipe to grep | find dir -type f -name '*.log' -print0 | xargs -0 grep PATTERN |
Practice drills
Use the sample demo/ tree (recreate it if you deleted files earlier) and try these without peeking. The point is to choose the predicate with intent, not to memorize syntax under pressure.
- List every regular file under
demowhose name ends in.log. - Find files larger than 50 KB.
- List
.jsfiles while skipping anything undernode_modules. - Find files modified more than 7 days ago.
- Count lines in every
.txtfile (case-insensitive), using-exec.
When you are ready to compare, here are solid answers — not the only ones, but clear and portable:
find demo -type f -name '*.log'
find demo -type f -size +50k
find demo -path '*/node_modules/*' -prune -o -type f -name '*.js' -print
find demo -type f -mtime +7
find demo -type f -iname '*.txt' -exec wc -l {} +
If you can work through those five comfortably, you already cover most real find work: locating files by what you know, skipping noise, and acting on the result. Start with -name and -type, then tighten with size, time, and -prune only when the first pass gets noisy.