Basic syntax
find [PATH...] [EXPRESSION]
If PATH is omitted, the current directory is used. EXPRESSION is a chain of
predicates and actions joined by an implicit -and.
find . # all files and directories from .
find /var/log -type f # files only (not directories)
find ~ -name '*.log' # by name, glob pattern
find / -maxdepth 2 -type d # directories, at most 2 levels deep
Type predicates
| predicate | meaning |
|---|---|
-type f | regular file |
-type d | directory |
-type l | symlink (see symbolic-link) |
-type s | socket |
-type p | named pipe (FIFO) |
-type b / -type c | block / character device |
Name and path
find . -name '*.py' # name by glob
find . -iname '*.PY' # like -name, but case-insensitive
find . -path '*/tests/*.py' # full path by glob
find . -regex '.*/test_[0-9]+\.py' # POSIX regex against the full path
Quotes are required. Without them, the shell expands * before find sees it.
By time
Time in find is measured in days for the base flags and in minutes for
-mmin/-amin/-cmin. Signs: -N (less than N), +N (more than N), N (exactly N).
| flag | what it matches |
|---|---|
-mtime N | modified N days ago |
-atime N | last accessed N days ago |
-ctime N | metadata changed N days ago (see inode) |
-mmin N | modified N minutes ago |
-newer FILE | newer than FILE |
find /var/log -type f -mtime -1 # modified in the last 24 hours
find /tmp -type f -mtime +7 # older than a week, candidates for cleanup
find . -mmin -10 # touched in the last 10 minutes
find . -newer Makefile # everything newer than Makefile
By size
Suffixes: c (bytes), k (KiB), M (MiB), G (GiB).
find . -type f -size +100M # files larger than 100 MiB
find /var/log -type f -size +10M -size -100M # 10..100 MiB
find . -type f -empty # empty files
find . -type d -empty # and empty directories
By permissions and ownership
find . -type f -perm -u+x # execute bit set for the owner
find . -perm -4000 # SUID files (see [[file-permissions]])
find /etc -user root -group root
find /home -nouser -o -nogroup # orphaned UID/GID entries
Actions
By default find prints paths (-print). Other options:
find . -name '*.tmp' -delete # delete (built-in action)
find . -name '*.log' -exec gzip {} \; # exec once per filefind . -name '*.log' -exec gzip {} + # batch (faster, like xargs)find . -type f -print0 | xargs -0 wc -l # classic pattern with -print0
The difference between \; and +:
\;runs the command once per file. With thousands of files this is slow.+collects arguments into a batch and runs the command fewer times. This is the right default choice.
-print0 + xargs -0
If filenames can contain spaces or newlines, plain xargs will break. The
null-delimiter approach handles that:
find . -type f -name '*.log' -print0 | xargs -0 grep -l 'ERROR'
This finds all .log files that contain ERROR. It is a common pairing with
cmd-grep.
Logic
find . \( -name '*.tmp' -o -name '*.bak' \) -delete # OR via -o, parentheses must be escaped
find . -name '*.py' -not -path '*/venv/*' # NOT via -not (or !)
find . -type f -name '*.log' -size +1M # implicit AND
fd as a modern alternative
fd (the fd-find package) is a simpler wrapper with better defaults: it
respects .gitignore, outputs color, supports parallel execution, and uses
-x for exec. When it is available, fd '\.py$' src is shorter and faster.
That said, find ships everywhere out of the box, so knowing its basics is
worth the time.