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:

GoalCommand
By namefind dir -name '*.log'
Case-insensitive namefind dir -iname '*.txt'
Files onlyfind dir -type f
Directories onlyfind dir -type d
Larger than 100 KBfind dir -type f -size +100k
Older than 7 daysfind dir -type f -mtime +7
Name OR namefind dir \( -name '*.log' -o -name '*.txt' \)
Delete matchesfind dir -type f -name '*.tmp' -delete
Run a commandfind dir -type f -exec wc -l {} +
Skip a folderfind dir -path '*/node_modules/*' -prune -o -type f -print
Pipe to grepfind 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.

  1. List every regular file under demo whose name ends in .log.
  2. Find files larger than 50 KB.
  3. List .js files while skipping anything under node_modules.
  4. Find files modified more than 7 days ago.
  5. Count lines in every .txt file (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.