find
is the standard utility for searching files and directories on Unix-like systems. It’s extremely powerful: filter by name, type, size, permissions, dates, contents, and even run commands on each result.
Basic syntax
find [PATHS...] [OPTIONS] [TESTS ...] [ACTIONS]
- PATHS: one or more starting points (default:
.
). - TESTS: filter conditions (e.g.,
-name
,-type
,-size
). - ACTIONS: what to do with the results (e.g.,
-print
,-exec
,-delete
).
Quick examples
All .log
files in the current directory (recursive):
find . -type f -name "*.log"
Case-insensitive:
find . -type f -iname "*.jpg"
Only in the current folder, without descending into subdirectories:
find . -maxdepth 1 -type f -name "*.sh"
Common filters
By name and path
find /var/log -type f -name "syslog*"
find ~ -type f -path "*/node_modules/*" # matches full path
find . -regex '.*\.\(jpg\|png\|gif\)$' # Emacs-style regex
By type
find /etc -type f # regular files
find /etc -type d # directories
find / -type l # symlinks
find . -type s # sockets
find . -type p # named pipes
By size
-size
units: c
=bytes, k
=KiB, M
=MiB, G
=GiB. Prefixes: -
“less than”, +
“more than”.
find . -type f -size +100M # >100 MiB
find . -type f -size -10k # <10 KiB
find . -type f -size 0 # exactly 0 bytes
By time
-mtime
days, -mmin
minutes (last modification); similar: -atime/-amin
(access), -ctime/-cmin
(metadata change).
find . -type f -mtime -1 # modified in the last 24h
find . -type f -mmin -30 # modified in the last 30 minutes
find /var/log -type f -mtime +7 # older than 7 days
Permissions, ownership, empties
find . -empty # empty files or dirs
find . -user alice # owner
find . -group www-data # group
find . -perm 0644 # exact permissions (octal)
find . -perm -0100 # contains execute bit for owner
Combining conditions
Logical operators: -a
/-and
(AND), -o
/-or
(OR), !
(NOT). Use parentheses for precedence; escape them in shell.
find . \( -name "*.png" -o -name "*.jpg" \) -type f ! -size 0
Excluding paths (pruning)
-prune
prevents descending into certain directories. Typical pattern to skip node_modules
and .git
:
find . -type d \( -name node_modules -o -name .git \) -prune -o -type f -print
Follow symlinks
find -L /path -type f -name "*.conf" # -L follows symbolic links
Printing results
-print
is implicit, but you can customize output with -printf
(GNU find
):
find . -type f -printf '%p %s bytes %TY-%Tm-%Td %TH:%TM\n'
Running commands on results
-exec
with \;
vs +
-exec ... \;
runs the command once per file.-exec ... +
batches multiple files into one execution (more efficient).
# Remove .tmp files (one call per file)
find . -type f -name "*.tmp" -exec rm -v {} \;
# Compress in batches (fewer calls)
find . -type f -name "\*.log" -mtime +7 -exec gzip -9 {} +
-ok
: interactive confirmation
find . -type f -name "*.bak" -ok rm {} \;
-print0
and xargs -0
(safe with spaces)
Use NUL terminators to handle names with spaces or newlines.
find . -type f -name "*.txt" -print0 | xargs -0 wc -l
Deleting safely
- Test first with
-print
to verify matches. - Prefer
-delete
over-exec rm
when possible; often safer and faster.
# DRY RUN
find . -type f -name "*.tmp" -print
# Actually delete
find . -type f -name "\*.tmp" -delete
To avoid unexpected removal of non-empty directories, some versions require -depth
when combining -delete
with directory patterns:
find . -depth -type d -name "__pycache__" -delete
Practical recipes
Files larger than 1 GiB, show “human” size:
find . -type f -size +1G -print0 | xargs -0 du -h
Search executable scripts with shebang:
find . -type f -perm -0100 -exec head -n1 {} \; | grep -n "^#!"
Find files modified today and open in editor:
find . -type f -daystart -mtime 0 -exec "$EDITOR" {} +
Exclude build folders and search C/C++ sources:
find . -type d \( -name build -o -name dist \) -prune -o \
-type f \( -name "*.c" -o -name "*.cpp" -o -name "*.h" \) -print
Find broken symlinks:
find . -xtype l
Clean up temporary files left by editors:
find . -type f \( -name "*~" -o -name ".*.swp" -o -name ".DS_Store" \) -delete
Order, depth, and performance
-maxdepth N
limits traversal;-mindepth N
skips initial levels.-mount
/-xdev
avoids crossing into other filesystems (useful on/
).-samefile
,-inum
, and-links
help with hard links and inodes.
# Search only on the current filesystem
find / -xdev -type f -size +1G -print
Advanced formatting with -printf
(GNU)
Specify fields as in printf(3)
:
%p
=path,%f
=name,%h
=dir,%s
=bytes%u
=user,%g
=group,%m
=permissions,%TY
/%Tm
/%Td
=date
find . -type f -printf '%m %u %g %s %p\n' | sort -k4,4n
Common pitfalls
- Shell glob vs find: quote patterns (
"*.txt"
) to avoid shell expansion. - Spaces in names: use
-print0
withxargs -0
or-exec ... {} +
. - Parentheses: remember backslashes:
\( ... \)
. - Compatibility: options like
-printf
are GNU; on macOS you may needfindutils
(e.g.,gfind
).
Minimal cheat sheet
# Names
find . -name "*.ext" # case-sensitive
find . -iname "*.ext" # case-insensitive
# Type
find . -type f|d|l
# Size
find . -size +100M # >100 MiB
# Time
find . -mtime -1 # modified < 1 day
# Depth
find . -maxdepth 2 -mindepth 1
# Exclusions
find . -path "*/.git/*" -prune -o -print
# Actions
find . -exec cmd {} ; # per-file
find . -exec cmd {} + # batch
find . -delete # delete (careful!)
# Name safety
find . -print0 | xargs -0 cmd
Conclusion
find
offers a rich grammar for querying the filesystem and automating workflows. Start with simple patterns, verify with -print
, then add -exec
or -delete
cautiously. With just a few well-chosen options, you can replace complex scripts and save time every day.