find
Locate files by name, type, size, age, and more
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.
Tests and actions, combined with 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
find's expression is evaluated left to right with short-circuit logic, same as &&/|| in
a shell. The order affects both correctness and speed: 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 decides whether -prune works at all. 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). The same idiom with ! -readable in front of it is how you
search a tree without the permission denied errors, which is
a better answer than 2>/dev/null for reasons of exit status as much as of noise.
-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 {} \;runscmdonce 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 whencmd's argument order doesn't matter.-deleteremoves 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 ... +. Use it when you needxargs-specific features like-P(parallelism).
See ls for reading the tree find walks, including the -lAR listing that
this page's own sample-file block is captured with.
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. Bulk rename a batch of files is that pairing
written out in full, for a job where getting it wrong renames the wrong thing.
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.
Sample files used on this page
Every example below was run against these files. Recreate them to follow along.
projects/ the tree every example below searches - ls -lAR projects
projects:
total 20
drwxr-xr-x 2 user user 4096 Jun 26 10:00 .hidden
drwxr-xr-x 2 user user 4096 Jun 26 10:00 backups
drwxr-xr-x 2 user user 4096 Jun 26 10:00 empty-dir
drwxr-xr-x 2 user user 4096 Jun 26 10:00 logs
drwxr-xr-x 3 user user 4096 Jun 26 10:00 src
projects/.hidden:
total 4
-rw-r--r-- 1 user user 13 Jun 24 10:00 .env
projects/backups:
total 16
-rw------- 1 user user 5120 May 31 2025 site-2026-01-01.tar.gz
-rw-r--r-- 1 user user 5120 Jun 15 15:30 site-2026-06-01.tar.gz
projects/empty-dir:
total 0
projects/logs:
total 6156
-rw-r--r-- 1 user user 10240 Jun 21 10:00 app.log
-rw-r--r-- 1 user user 6291456 Jun 20 10:00 big.log
lrwxrwxrwx 1 user user 14 Jun 25 10:00 main-link.js -> ../src/main.js
lrwxrwxrwx 1 user user 12 Jun 25 10:00 rotated-link -> app.log.1.gz
projects/src:
total 12
-rwxrwxrwx 1 user user 8 Jun 23 10:00 main.js
-rw-r--r-- 1 user user 8 Jun 1 09:00 util.js
drwxr-xr-x 2 user user 4096 Jun 26 10:00 vendor
projects/src/vendor:
total 4
-rw-r--r-- 1 user user 7 Jun 22 10:00 lib.js
Matching by name
The most common find, by a mile. Note the | sort on examples that match more than one file: find prints in directory order, which is whatever order the filesystem hands back, so sorting is what makes a result you can compare against.
Find files by extension
find projects -name "*.js" | sort
-name matches against the filename only (not the full path), and is case-sensitive.
Show output
projects/logs/main-link.js
projects/src/main.js
projects/src/util.js
projects/src/vendor/lib.js
Match a name case-insensitively
find projects -iname "*.LOG" | sort
-iname is the case-insensitive version of -name, matching app.log, App.Log, APP.LOG.
Show output
projects/logs/app.log
projects/logs/big.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
projects/.hidden/.env
Match against the full path, not just the filename
find projects -path "*/vendor/*" | sort
-path matches the whole path so far, useful for excluding or targeting a specific subdirectory by name anywhere in the tree.
Show output
projects/src/vendor/lib.js
Match a name using extended regex
find projects -regex ".*\.\(js\|log\)$" | sort
-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
projects/logs/app.log
projects/logs/big.log
projects/logs/main-link.js
projects/src/main.js
projects/src/util.js
projects/src/vendor/lib.js
Filtering by type
Files, directories, symlinks: find sees them all differently.
Find only regular files
find projects -type f | sort
-type f excludes directories, symlinks, and other special files, usually what you want before acting on results.
Show output
projects/.hidden/.env
projects/backups/site-2026-01-01.tar.gz
projects/backups/site-2026-06-01.tar.gz
projects/logs/app.log
projects/logs/big.log
projects/src/main.js
projects/src/util.js
projects/src/vendor/lib.js
Find only directories
find projects -type d | sort
Includes the starting directory itself (projects) unless you filter it out, which trips people up when piping straight into a delete.
Show output
projects
projects/.hidden
projects/backups
projects/empty-dir
projects/logs
projects/src
projects/src/vendor
Find symlinks
find projects -type l | sort
-type l matches the symlink itself, not whatever it points to. ln has the counterpart, -xtype l, for the broken ones.
Show output
projects/logs/main-link.js
projects/logs/rotated-link
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
projects/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
projects/logs/rotated-link
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
projects/logs/big.log
Find files smaller than a given size
find projects -size -1k
-1k means strictly less than 1 kilobyte, useful for finding suspiciously empty-looking files.
Find files within a size range
find projects -size +1M -size -10M
The -size tests combine with the implicit AND, giving you a range.
Show output
projects/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 projects/logs/big.log
10240 projects/logs/app.log
5120 projects/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 modified in the last 90 minutes
find projects -mmin -90
-mmin is the minutes version of -mtime, for finer-grained recency than a full day.
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 modified today, by the calendar, not the last 24 hours
find projects -daystart -mtime 0
-mtime 0 alone means "less than 24 hours ago," which drifts with what time you run the command. -daystart measures from the start of today instead, so "0 days" means "today's calendar date."
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
projects/backups/site-2026-01-01.tar.gz
Find files newer than a reference file
find projects -newer projects/src/util.js -type f | sort
-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. touch -d puts that marker wherever you need it.
Show output
projects/.hidden/.env
projects/backups/site-2026-06-01.tar.gz
projects/logs/app.log
projects/logs/big.log
projects/src/main.js
projects/src/vendor/lib.js
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 | sort
-maxdepth 1 stops find from descending past the starting directory's immediate children.
Show output
projects
projects/.hidden
projects/backups
projects/empty-dir
projects/logs
projects/src
Skip the top levels and only match deeper files
find projects -mindepth 2 -type f | sort
-mindepth is the inverse of -maxdepth, excluding files that are too shallow instead of too deep.
Show output
projects/.hidden/.env
projects/backups/site-2026-01-01.tar.gz
projects/backups/site-2026-06-01.tar.gz
projects/logs/app.log
projects/logs/big.log
projects/src/main.js
projects/src/util.js
projects/src/vendor/lib.js
Skip an entire subdirectory
find projects -type d -name vendor -prune -o -type f -print | sort
-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
projects/.hidden/.env
projects/backups/site-2026-01-01.tar.gz
projects/backups/site-2026-06-01.tar.gz
projects/logs/app.log
projects/logs/big.log
projects/src/main.js
projects/src/util.js
Stay on one filesystem
sudo find / -xdev -name "*.log" -size +100M
-xdev stops find descending into anything mounted elsewhere, so a search of / doesn't wander into /proc, a network share or an external disk. Essential when searching from the root of a machine with mounts you don't want to walk, and the reason a whole-system search can otherwise take minutes.
Find files with an exact permission mode
find projects -perm 777 | sort
-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
projects/logs/main-link.js
projects/logs/rotated-link
projects/src/main.js
Find files where the owner can execute, regardless of other bits
find projects -perm -u+x
The - prefix switches to "at least these bits set" matching. This finds every directory (always executable/traversable) plus any file the owner can run.
Find files owned by the current user
find projects -user "$(whoami)"
-user filters by owner name (or numeric UID).
Acting on matches
-exec, -delete, and piping to other commands.
Run a command on each match
find projects -name "*.tar.gz" -exec ls -lh {} \; | sort
{} 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------- 1 user user 5.0K May 31 2025 projects/backups/site-2026-01-01.tar.gz
-rw-r--r-- 1 user user 5.0K Jun 15 15:30 projects/backups/site-2026-06-01.tar.gz
Batch multiple matches into fewer command invocations
find projects -type f -exec md5sum {} + | sort -k2
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
370410133629531d6f86547621ad819d projects/.hidden/.env
4f3f1f5fda178eee12ed001a18a61d9e projects/backups/site-2026-01-01.tar.gz
27b76a39842f71707d6028436b0ba068 projects/backups/site-2026-06-01.tar.gz
a3f149852edbdb4a491db2749263510b projects/logs/app.log
fcebc6752f074048b314629e78a9765a projects/logs/big.log
d4c73f111c0ef6d8782c0d6f9ef2bca4 projects/src/main.js
fc50f2bdf153bce5a7b24c2dabf14bb2 projects/src/util.js
73441ca386234c947b81650654869102 projects/src/vendor/lib.js
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 | sort
For a single exclusion, -not -path is shorter than the -prune idiom. Use -prune instead when you also need find to skip descending into a large excluded directory for performance.
Show output
projects/.hidden/.env
projects/backups/site-2026-01-01.tar.gz
projects/backups/site-2026-06-01.tar.gz
projects/logs/app.log
projects/logs/big.log
projects/src/main.js
projects/src/util.js
Match either of two patterns
find projects \( -name "*.js" -o -name "*.log" \) | sort
-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
projects/logs/app.log
projects/logs/big.log
projects/logs/main-link.js
projects/src/main.js
projects/src/util.js
projects/src/vendor/lib.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
projects/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.
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 projects/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