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
-printto verify matches. - Prefer
-deleteover-exec rmwhen 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 Nlimits traversal;-mindepth Nskips initial levels.-mount/-xdevavoids crossing into other filesystems (useful on/).-samefile,-inum, and-linkshelp 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
-print0withxargs -0or-exec ... {} +. - Parentheses: remember backslashes:
\( ... \). - Compatibility: options like
-printfare 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.