find Command Examples: Search Files by Name, Size, Date and More
On this page
- Basic syntax
- Find files by name
- Find by type
- Combining tests
- Find by size
- Find by modification time
- Find by owner and permissions
- Limiting depth
- Skipping directories with -prune
- Running commands with -exec
- Deleting files with -delete
- Using find with xargs
- Custom output with -printf (GNU only)
- Quick reference
find walks a directory tree and prints every file that matches the tests you give it. It can search by name, type, size, age, owner and permissions, and it can run a command on each result. Once you know a dozen patterns, you will use it every day.
The examples here use GNU find, the version on almost every Linux system. macOS and the BSDs ship a different find; most examples work the same, and the differences are pointed out where they matter.
Basic syntax
find [where to look] [tests and actions]
The starting point comes first, then the expression. With no action, find prints the path of each match:
$ find . -name "*.log"
./old.log
./logs/app.log
GNU find uses the current directory if you leave out the path, but macOS find requires it, so get used to writing find .. You can also give several starting points: find /etc /usr/local/etc -name "*.conf".
Find files by name
$ find . -name "report.pdf" # exact name
$ find . -name "*.jpg" # all JPEG files
$ find . -iname "*.jpg" # case-insensitive: .jpg, .JPG, .Jpg
$ find /etc -name "*.conf" # search a specific directory
Always quote the pattern. Without quotes, the shell expands *.jpg against the files in your current directory before find even runs, which gives wrong results or an error.
-name matches only the file name, not the directory part. To match against the whole path, use -path:
$ find . -path "*/config/*.yml"
Find by type
$ find . -type f # regular files only
$ find . -type d # directories only
$ find . -type l # symbolic links
$ find . -type d -empty # empty directories
$ find . -type f -empty # empty files
Combining tests
Tests written one after another must all match (an implied AND). Use -o for OR, -not (or !) for NOT, and escaped parentheses to group:
$ find . -type f \( -name "*.c" -o -name "*.h" \)
$ find . -type f -not -name "*.log"
The parentheses matter. AND binds more tightly than OR, so without them find . -name "*.c" -o -name "*.h" -print prints only the .h files, because -print is attached to the second test only. When in doubt, add parentheses.
Find by size
$ find . -type f -size +100M # larger than 100 MiB
$ find . -type f -size -10k # smaller than 10 KiB
$ find /var/log -type f -size +1G # larger than 1 GiB
Units are c (bytes), k, M and G, all powers of 1024. A + means "more than" and a - means "less than". One quirk: find rounds sizes up to whole units before comparing, so -size -1M matches only empty files. Use a smaller unit (-size -1024k) for precise "less than" tests.
To see the sizes, hand the results to ls or du:
$ find . -type f -size +100M -exec ls -lh {} +
-rw-r--r-- 1 alice alice 150M Oct 25 09:41 ./src/big.iso
Find by modification time
$ find . -type f -mtime -1 # modified in the last 24 hours
$ find . -type f -mtime +30 # modified more than 30 days ago
$ find . -type f -mmin -15 # modified in the last 15 minutes
$ find . -type f -newer config.yml # changed more recently than config.yml
$ find . -type f -newermt 2026-10-01 # modified after a date
-mtime counts in 24-hour periods and ignores fractions. A file that is 30.9 days old counts as 30, so -mtime +30 really means "at least 31 days old". -atime and -ctime work the same way for access time and status-change time, and -amin/-cmin are the minute versions.
Find by owner and permissions
$ find /home -user alice # owned by user alice
$ find /srv -group www-data # owned by group www-data
$ find . -nouser # owner no longer exists
$ find . -type f -perm 644 # permissions exactly 644
$ find . -type f -perm -u+x # owner has execute (at least)
$ find . -type f -perm -o+w # writable by everyone: worth checking
With -perm, a plain mode must match exactly, while a leading - means "all of these bits are set". GNU find also has -perm /mode for "any of these bits". The permissions guide explains the numbers.
Limiting depth
$ find . -maxdepth 1 -type f # only the current directory, no subdirectories
$ find . -mindepth 2 -name "*.md" # skip the top level
-maxdepth applies to the whole search wherever you put it, but it reads best right after the path, and some versions of GNU find print a warning if it comes after other tests.
Skipping directories with -prune
Searching a project usually means wading through node_modules, vendor or .git. -prune tells find not to descend into a directory at all, which is also much faster than filtering afterwards:
$ find . -name node_modules -prune -o -type f -name "*.js" -print
$ find . \( -name node_modules -o -name .git \) -prune -o -type f -print
Read it as: "if this is node_modules, prune it; otherwise, if it is a matching file, print it". The explicit -print at the end is required; without it, find would also print the pruned directory names.
Running commands with -exec
-exec runs a command for the results. {} stands for the file name, and the command ends with either \; or +:
$ find . -name "*.txt" -exec wc -l {} \; # runs wc once per file
$ find . -name "*.txt" -exec wc -l {} + # runs wc once with many files
The + form is much faster for many files because it starts the command only a few times. Use \; when the command accepts only one file at a time, or when {} must appear somewhere other than the end. Both forms handle spaces and odd characters in names safely.
A few common tasks:
$ find . -name "*.php" -exec grep -l "mysql_connect" {} + # which files contain a string
$ find . -type f -name "*.sh" -exec chmod +x {} + # make scripts executable
$ find . -type d -exec chmod 755 {} + # fix directory permissions
$ find . -type f -exec chmod 644 {} + # fix file permissions
When you need a pipeline or several commands per file, run a small shell:
$ find . -type f -name "*.txt" -exec sh -c 'for f; do echo "processing $f"; done' sh {} +
Use -ok instead of -exec to be asked before each command runs.
Deleting files with -delete
-delete removes files immediately, with no confirmation and no undo. Always run the same command with -print (or no action) first, check the list, and only then swap in -delete. Put -delete last: find evaluates in order, so find . -delete -name "*.log" deletes everything under the current directory.$ find /tmp/myapp -type f -name "*.log" -mtime +7 -print # check first
$ find /tmp/myapp -type f -name "*.log" -mtime +7 -delete # then delete
-delete works with GNU and BSD find. It removes a matching directory only if that directory is empty, so it cannot remove, say, every build directory along with its contents. For that, use -exec rm -r {} +, with the same check-first care.
Using find with xargs
Piping find into xargs is a classic alternative to -exec ... +, and it lets you run jobs in parallel with xargs -P. Always pair -print0 with xargs -0, which separate names with a null byte instead of a newline, so names with spaces or newlines cannot break the command:
$ find . -type f -name "*.log" -print0 | xargs -0 ls -l
$ find . -type f -name "*.png" -print0 | xargs -0 -P 4 -n 10 optipng
More on xargs, including -I {}, is in the xargs and pipes guide.
Custom output with -printf (GNU only)
GNU find can format its own output. This lists files with their size in bytes, largest last:
$ find . -type f -printf '%s %p\n' | sort -n | tail -5
Other useful directives are %TY-%Tm-%Td (modification date), %u (owner) and %m (octal permissions). macOS find has no -printf; use -exec stat -f '%z %N' {} + there, or install GNU findutils. Every option is documented in the find(1) man page.
Quick reference
| Goal | Command |
|---|---|
| By name, any case | find . -iname "*.jpg" |
| Only directories | find . -type d |
| Bigger than 100 MB | find . -type f -size +100M |
| Changed in last day | find . -type f -mtime -1 |
| Older than 30 days | find . -type f -mtime +30 |
| Current dir only | find . -maxdepth 1 -type f |
| Skip a directory | find . -name vendor -prune -o -type f -print |
| Run a command on all | find . -name "*.sh" -exec chmod +x {} + |
| Safe pipe to xargs | find . -type f -print0 | xargs -0 ... |
| Delete (check first) | find . -name "*.tmp" -type f -delete |