find

Locate files by name, type, size, age, and more

Updated 2026-07-05

find walks a directory tree and prints, or acts on, every file that matches a set of tests you give it. Where locate searches a pre-built index of filenames (fast, but can be stale and only matches names), find inspects the filesystem live: name, type, size, age, permissions, ownership, all of it, right now.

The mental model: tests, actions, and an implicit AND

A find command is a directory to start from, followed by a chain of tests and actions:

find ~/projects -name "*.log" -size +10M

Each test (-name, -size, -mtime, …) either matches a given file or it doesn't. Chain several and find combines them with an implicit AND: a file has to pass every test to be included. Use -o for OR, and parentheses (escaped as \( \) in most shells) to group:

find . \( -name "*.js" -o -name "*.log" \) -not -path "*/vendor/*"

By default the only "action" is -print (implied if you don't specify one), but -exec, -delete, and -printf let you act on or format each match directly, without piping to another command.

Tests evaluate left to right, and that's a feature

find's expression is evaluated left to right with short-circuit logic, same as &&/|| in a shell. This matters for both correctness and performance: put your cheapest, most-selective test first (usually -name or -type) so expensive tests like -exec only run against candidates that already passed the cheap filters. It also matters for -prune. To skip a whole directory, -prune has to come before whatever test would otherwise print or recurse into it (see the "skip a directory" example).

-exec vs -delete vs piping to xargs

Three ways to act on matches, in order of how much you should trust yourself with them:

  • -exec cmd {} \; runs cmd once per matched file. Safe and simple, but slow on huge result sets since it forks once per file.
  • -exec cmd {} + batches as many matches as fit on one command line into fewer invocations. Faster, but only works when cmd's argument order doesn't matter.
  • -delete removes matches directly, no subprocess at all. The fastest option and the most dangerous, because there's no confirmation step.
  • Piping to xargs (find ... -print0 | xargs -0 cmd) is the classic alternative to -exec ... +. Reach for it when you need xargs-specific features like -P (parallelism).

Filenames with spaces and newlines

Piping find's default newline-separated output into a loop or xargs breaks the moment a filename contains a space or newline. -print0 separates matches with a NUL byte instead, which can't appear in a filename; pair it with xargs -0 or while IFS= read -r -d '' f. This isn't a hypothetical edge case. It bites the first time your script meets a file like Meeting Notes (final).docx.

Permission tests are exact-match by default

-perm 644 matches files whose mode is exactly 644, not "at least these bits." To test "this bit is set, I don't care about the rest," prefix with -: -perm -u+x matches anything the owner can execute, regardless of group/other bits. This trips people up constantly; if your -perm test is matching nothing, check whether you meant the exact form or the --prefixed "at least" form.

Matching by name

The most common find, by a mile.

Find files by extension

find ~/projects -name "*.js"

`-name` matches against the filename only (not the full path), and is case-sensitive.

Show output
./logs/main-link.js
./src/util.js
./src/vendor/lib.js
./src/main.js

Match a name case-insensitively

find ~/projects -iname "*.LOG"

`-iname` is the case-insensitive version of `-name`, matching `app.log`, `App.Log`, `APP.LOG`.

Show output
./logs/big.log
./logs/app.log

Match hidden (dotfiles) files

find ~/projects -name ".*" -type f

A leading `.` isn't special to `find` the way it is to the shell's globbing. `-name ".*"` matches dotfiles like a normal pattern.

Show output
./.hidden/.env

Match against the full path, not just the filename

find ~/projects -path "*/vendor/*"

`-path` matches the whole path so far, useful for excluding or targeting a specific subdirectory by name anywhere in the tree.

Show output
./src/vendor
./src/vendor/lib.js

Match a name using extended regex

find ~/projects -regex ".*\.\(js\|log\)$"

`-regex` matches the entire path against a regex (BRE by default; add `-regextype posix-extended` for ERE syntax). More powerful than `-name` when you need alternation or anchors.

Show output
./logs/main-link.js
./logs/big.log
./logs/app.log
./src/util.js
./src/vendor/lib.js
./src/main.js

Filtering by type

Files, directories, symlinks: find sees them all differently.

Find only regular files

find ~/projects -type f

`-type f` excludes directories, symlinks, and other special files, usually what you want before acting on results.

Show output
./backups/site-2026-06-01.tar.gz
./backups/site-2026-01-01.tar.gz
./logs/big.log
./logs/app.log
./src/util.js
./src/vendor/lib.js
./src/main.js
./.hidden/.env

Find only directories

find ~/projects -type d

Includes the starting directory itself (`.`) unless you filter it out.

Show output
.
./backups
./logs
./src
./src/vendor
./.hidden
./empty-dir

Find symlinks

find ~/projects -type l

`-type l` matches the symlink itself, not whatever it points to.

Show output
./logs/main-link.js

Find symlinks that point to a regular file

find ~/projects -type l -xtype f

`-xtype` follows the link before testing type. This finds symlinks whose *target* is a regular file, as opposed to a broken link or a link to a directory.

Show output
./logs/main-link.js

Find broken symlinks

find ~/projects -xtype l

When a symlink's target has been moved or deleted, resolving its type fails and falls back to reporting `l`. This is the standard "find broken links" idiom.

Show output
./logs/main-link.js

Filtering by size

Track down what's actually using disk space.

Find files larger than a given size

find ~/projects -size +1M

`+1M` means strictly greater than 1 megabyte. Suffixes: `c` bytes, `k` kilobytes, `M` megabytes, `G` gigabytes.

Show output
./logs/big.log

Find files within a size range

find ~/projects -size +1M -size -10M

Two `-size` tests combine with the implicit AND, giving you a range.

Show output
./logs/big.log

Find and list the biggest files, largest first

find ~/projects -type f -printf "%s %p\n" | sort -rn | head -3

`-printf "%s %p\n"` prints size in bytes then path; piping through `sort -rn` ranks by size numerically, largest first.

Show output
6291456 ./logs/big.log
10240 ./logs/app.log
5120 ./backups/site-2026-06-01.tar.gz

Filtering by age

mtime, mmin, and finding what changed recently (or didn't).

Find files modified in the last 24 hours

find ~/projects -mtime -1

`-mtime -1` means modified less than 1 day ago. The sign convention is the same as `-size`: `-N` is less than, `+N` is more than, `N` is exactly.

Find files accessed recently, not just modified

find ~/projects -type f -amin -120

`-amin`/`-atime` test access time instead of modification time, useful for finding files nothing has *read* in a while, as opposed to files nobody has *written* to. Many systems mount filesystems with `noatime` for performance, which makes this test a no-op there.

Find files not modified in over a year

find ~/projects -type f -mtime +365

A common first step before archiving or deleting old backups. See the danger note below before adding `-delete`.

Show output
./backups/site-2026-01-01.tar.gz

Find files newer than a reference file

find ~/projects -newer src/util.js -type f

`-newer` compares modification times against another file instead of the current time, useful for "what changed since the last deploy", using a marker file's timestamp as the cutoff.

Show output
./backups/site-2026-06-01.tar.gz
./logs/big.log
./logs/app.log
./src/vendor/lib.js
./src/main.js
./.hidden/.env

Depth and pruning

Controlling how far find descends, and skipping subtrees entirely.

Limit the search to the top level only

find ~/projects -maxdepth 1 -type d

`-maxdepth 1` stops find from descending past the starting directory's immediate children.

Show output
.
./backups
./logs
./src
./.hidden
./empty-dir

Skip the top levels and only match deeper files

find ~/projects -mindepth 2 -type f

`-mindepth` is the inverse of `-maxdepth`, excluding files that are too shallow instead of too deep.

Show output
./backups/site-2026-06-01.tar.gz
./backups/site-2026-01-01.tar.gz
./logs/big.log
./logs/app.log
./src/util.js
./src/vendor/lib.js
./src/main.js
./.hidden/.env

Skip an entire subdirectory

find ~/projects -type d -name vendor -prune -o -type f -print

`-prune` has to come before the `-o` so it applies to the vendor match specifically; without it, `find` would still descend into `vendor/` looking for files. This idiom (`-name X -prune -o ... -print`) is the standard way to exclude a directory like `node_modules` or `.git`.

Show output
./backups/site-2026-06-01.tar.gz
./backups/site-2026-01-01.tar.gz
./logs/big.log
./logs/app.log
./src/util.js
./src/main.js
./.hidden/.env

Stay on one filesystem

find / -xdev -maxdepth 1 -type d

`-xdev` (a.k.a. `-mount`) prevents find from crossing into mounted filesystems (network shares, other drives, `/proc`) when searching from a high-level directory like `/`.

Show output
/
/tmp
/boot

Permissions and ownership

Exact-match vs at-least-these-bits: see the note in the intro.

Find files with an exact permission mode

find ~/projects -perm 777

`-perm 777` (no prefix) matches only files whose mode is *exactly* `777`. Note this also matches symlinks, whose own permissions are always shown as `777` regardless of the target's real permissions.

Show output
./logs/main-link.js
./src/main.js

Acting on matches

-exec, -delete, and piping to other commands.

Run a command on each match

find ~/projects -name "*.tar.gz" -exec ls -lh {} \;

`{}` is replaced with the matched path, and `\;` (escaped so the shell doesn't eat the semicolon) ends the `-exec` expression. Runs `ls -lh` once per file.

Show output
-rw-r--r-- 1 user user 5.0K Jun 15 15:30 ./backups/site-2026-06-01.tar.gz
-rw------- 1 user user 5.0K May 31  2025 ./backups/site-2026-01-01.tar.gz

Batch multiple matches into fewer command invocations

find ~/projects -type f -exec md5sum {} +

Ending with `+` instead of `\;` passes as many matched files as fit on one command line to a single `md5sum` invocation, instead of spawning one process per file. Much faster over large result sets.

Show output
./backups/site-2026-06-01.tar.gz
./backups/site-2026-01-01.tar.gz
./logs/big.log
./logs/app.log
./src/util.js
./src/vendor/lib.js
./src/main.js
./.hidden/.env

Delete matches directly

find ~/projects -type f -name "*.tar.gz" -mtime +365 -delete

`-delete` removes matches with no subprocess and no confirmation. Always run the identical command with `-print` (or no action) first and read the list before adding `-delete`.

Remove empty directories

find ~/projects -type d -empty -delete

Combine `-type d` and `-empty` to target only directories with nothing in them.

Feed matches to xargs for parallel processing

find ~/projects -type f -name "*.log" -print0 | xargs -0 -P 4 gzip

`-print0` / `xargs -0` handles filenames with spaces safely (see the note in the intro); `xargs -P 4` runs up to 4 `gzip` processes in parallel.

Combining tests with logic

AND is implicit; OR and NOT need to be spelled out.

Negate a test

find ~/projects -not -name "*.js"

`-not` (or `!`, escaped as `\!` in most shells) inverts the following test.

Exclude an entire path pattern without -prune

find ~/projects -not -path "*/vendor/*" -type f

For a single exclusion, `-not -path` is shorter than the `-prune` idiom. Reach for `-prune` instead when you also need find to skip *descending into* a large excluded directory for performance.

Show output
./backups/site-2026-06-01.tar.gz
./backups/site-2026-01-01.tar.gz
./logs/big.log
./logs/app.log
./src/util.js
./.hidden/.env

Match either of two patterns

find ~/projects \( -name "*.js" -o -name "*.log" \)

`-o` is OR; the parentheses are required here because `find`'s default AND would otherwise bind more tightly than you want. Escape them (`\(` `\)`) so the shell passes them through literally.

Show output
./logs/main-link.js
./logs/big.log
./logs/app.log
./src/util.js
./src/vendor/lib.js
./src/main.js

Combine a name filter with an age filter

find ~/projects -type f -name "*.tar.gz" -mtime +365

Multiple tests chained with no explicit operator are ANDed together. This narrows straight to old, matching backups.

Show output
./backups/site-2026-01-01.tar.gz

Practical one-liners

Common real-world tasks that combine several of the tests above.

Count files by type

find ~/projects -type f | wc -l

A quick sanity check before a bulk operation: how many files am I actually about to touch?

Show output
8

Find and grep in one pipeline

find ~/projects -name "*.js" -print0 | xargs -0 grep -l "TODO"

Narrow the file set with `find` first, then hand it to `grep`. Faster than `grep -r` when you need find's richer filtering (age, size, permissions) as well as a pattern match. See [grep](/commands/grep/).

Find the single largest file under a path

find ~/projects -type f -printf "%s %p\n" | sort -rn | head -1

The one-result version of the size-ranking example above, handy inline in a script.

Show output
6291456 ./logs/big.log

Count matches across two patterns

find ~/projects \( -name "*.log" -o -name "*.tar.gz" \) | wc -l

Group an OR expression, then pipe the whole result through `wc -l` for a total count instead of a list.

Show output
4