I learned to respect find after aiming a cleanup command one directory higher than intended. Nothing catastrophic happened—the preview caught it—but the wall of unexpected paths made the lesson permanent: the starting point is part of the query, and printing matches is a separate phase from changing them.

A compact syntax reference

  • -name PATTERN matches a case-sensitive basename; -iname ignores case.

  • -type f, -type d, and -type l select regular files, directories, and symbolic links.

  • -size +100M selects sizes greater than 100 rounded MiB units; -100M means less and 100M means exactly that rounded unit count.

  • -print emits matching paths; GNU -printf can emit path, size, time, and other fields.

  • -exec command {} + batches matching paths as arguments instead of launching once per path.

  • -delete removes matches and must come after carefully tested selection predicates.

Find a file by name

Terminalbash
find ./project -type f -name 'helloworld.pdf'
find ./project -type f -iname '*.pdf'

What controls the match

  • ./project is the traversal root; choosing it narrowly reduces time, permission errors, and accidental matches.

  • -type f excludes directories, links, sockets, and device nodes.

  • -name and -iname compare the basename rather than the full path.

  • Quotes keep *.pdf intact so find, not the current shell directory, evaluates the wildcard.

Filter by filesystem object type

Terminalbash
find ./project -type f -print
find ./project -type d -print
find ./project -type l -print
  • -type examines the filesystem object selected under the current link-following mode.

  • GNU find does not follow symbolic links by default (-P behavior).

  • -L follows links and changes traversal and type tests; do not add it casually around cyclic or untrusted trees.

  • -xtype is available when you specifically need the complementary link-target behavior.

Find large files and understand the unit

Terminalbash
find ./project -type f -size +100M -print
find ./project -type f -size +104857600c -print

Why these two queries can differ

  • M means 1,048,576-byte units in GNU find, while c means bytes.

  • +n means greater than n units after the file size is rounded up to that unit.

  • +104857600c is a byte-level test for files larger than 100 MiB.

  • -size uses apparent length from filesystem metadata; sparse-file disk consumption can be much smaller.

Terminalbash
find ./project -type f -size +10M -printf '%s bytes	%p
'
find ./project -type f -size +10M -printf '%s	%p\0' | sort -z -n | tr '\0' '
'

What makes the output safer

  • %s prints apparent size in bytes and %p prints the matched path.

  • \0 in the displayed command terminates records with NUL, so embedded spaces and newlines do not split filenames inside the pipeline.

  • sort -z -n understands NUL-delimited records and compares the leading size numerically.

  • The final tr is display-oriented; unusual names containing newlines can again span visual lines after conversion.

Search only C source files for text

Terminalbash
find ./src -type f -name '*.c' -exec grep -Hn -i -- 'main' {} +

How find and grep divide the work

  • find selects regular files whose basename ends in .c.

  • -exec … {} + appends multiple selected paths per grep invocation, reducing process overhead.

  • -Hn prints filenames and line numbers; -i makes text matching case-insensitive.

  • -- ends grep option parsing so a search string beginning with a hyphen is not treated as a flag.

Combine alternatives without precedence surprises

Terminalbash
find ./src -type f ( -name '*.c' -o -name '*.h' ) -print

Why the parentheses matter

  • Adjacent tests imply logical AND, while -o means OR.

  • AND binds more tightly than OR, so grouping makes -type f apply to both filename alternatives.

  • The backslashes stop the shell from treating parentheses as shell syntax.

  • Expression evaluation short-circuits from left to right, which also affects which actions run.

Handle permission errors without hiding real failures

Terminalbash
find ./project -type f -name '*.log' -print 2>find-errors.log
printf 'find exit status: %s
' "$?"

What redirection tells you

  • 2> sends only standard error to the named file; it does not grant access or suppress the exit status.

  • $? must be read immediately after find because the next command replaces it.

  • A nonzero status means the traversal was not entirely successful even when some paths were printed.

  • Avoid adding sudo by reflex; narrow the starting point or correct intended access instead.

Preview first, delete in a separate decision

Terminalbash
find ./build-cache -depth -type f -name '*.tmp' -mtime +7 -print

What must be true before deletion

  • The starting directory is the intended disposable cache, not ., /tmp, a home directory, or a variable you have not inspected.

  • -depth visits children before their directory and mirrors traversal implied by GNU -delete.

  • -mtime +7 uses completed 24-hour periods and does not mean “older than exactly 168 hours” in every intuitive boundary case.

  • Read the complete preview, check the exit status, and rerun after any expression change.

Terminalbash
find ./build-cache -depth -type f -name '*.tmp' -mtime +7 -delete

Risk level: destructive. Review the command before running it.

Why deletion is deliberately last

  • -delete permanently removes each matched file; there is no trash or built-in undo.

  • Placing selection tests before the action makes the intended evaluation order readable.

  • GNU -delete implies depth-first traversal, which is why the preview explicitly used -depth.

  • Do not combine -delete casually with -prune; depth-first behavior makes pruning ineffective.

A dependable mental model

Read a find invocation as a small program: establish the traversal boundary, filter objects, combine conditions with explicit grouping, and finally choose an action. Most dangerous mistakes happen when the boundary is vague, the shell rewrites a pattern, or a destructive action is added before the selection has been observed.