open atlas
↑ Back to track
Command line CLI · 04 · 02

Finding files with find

find searches a directory tree by name, type, size, or modification time. -exec runs a command on each result; -print0 with xargs -0 handles filenames with spaces safely.

CLI Junior ◷ 22 min
Level
FoundationsJuniorMiddleSenior

Globs only search the current directory. When you need to hunt through an entire directory tree — find every .log file under /var, every file larger than 100 MB, or every file touched in the last 24 hours — you need find. It descends recursively, filters on almost any file attribute, and can run a command on every match.

By the end of this lesson you will write find commands that filter by name, type, size, and modification time, and safely execute actions on the results.

Goal

After this lesson you can use find to search a directory tree by name (-name), type (-type), size (-size), and modification time (-mtime); execute a command on each result with -exec; and handle filenames containing spaces safely with -print0 and xargs -0.

1

Basic syntax: find <path> <expression>. The first argument is the root of the search; everything after it is a predicate. Without any predicate, find prints every file it finds:

find /var/log          # list every file under /var/log recursively
find .                 # list every file under the current directory

Predicates are evaluated left to right and combined with implicit AND. Add -maxdepth N to limit recursion depth.

2

-name and -iname filter by filename pattern. The pattern uses shell globs but must be quoted so the shell does not expand it before find sees it:

find . -name "*.log"           # files ending in .log
find /etc -name "nginx.conf"   # exact name search
find . -iname "readme*"        # case-insensitive; matches README.md, readme.txt

-name matches only the filename, not the full path. To match against the path, use -path "*/dir/*.log".

3

-type filters by entry type. Common values:

FlagMeaning
-type fregular file
-type ddirectory
-type lsymbolic link
find . -type f -name "*.sh"    # only files, not directories named *.sh
find /tmp -type d              # list every directory under /tmp

Combining -type f with -name is the most common pattern.

4

-size filters by file size. Suffixes: c (bytes), k (kibibytes), M (mebibytes), G (gibibytes). Use + for “greater than” and - for “less than”:

find /var/log -size +10M        # files larger than 10 MiB
find . -size -1k                # files smaller than 1 KiB
find /home -type f -size +100M  # large files consuming space

-mtime N filters by modification time in days. +N means “older than N days”; -N means “modified within the last N days”:

find . -mtime -1    # modified in the last 24 hours
find . -mtime +30   # not modified in over 30 days
find . -mmin -60    # modified in the last 60 minutes (-mmin uses minutes)
5

-exec runs a command on each match. The {} placeholder is replaced by the matched filename. The expression must end with \; (one invocation per file) or + (batch all files into one invocation):

# Delete every .tmp file (one rm call per file)
find . -name "*.tmp" -exec rm {} \;

# Delete every .tmp file (one rm call with all files — faster)
find . -name "*.tmp" -exec rm {} +

# Print size of each file over 10M
find /var -size +10M -exec du -sh {} \;

-print0 with xargs -0 is the safest alternative when filenames may contain spaces or newlines:

find . -name "*.log" -print0 | xargs -0 gzip

-print0 separates results with a null byte instead of a newline; xargs -0 reads null-separated input. This survives filenames like my file.log.

Worked example

Finding and compressing old logs.

You have a server with logs accumulating under /var/log/app. You want to gzip every .log file not modified in the past 7 days:

find /var/log/app -type f -name "*.log" -mtime +7 -print0 \
  | xargs -0 gzip --best

Breaking it down:

  • -type f — only regular files, skip directories
  • -name "*.log" — only log files (pattern quoted so the shell doesn’t expand it)
  • -mtime +7 — older than 7 days
  • -print0 | xargs -0 gzip --best — pass all matches to gzip in one batch, null-delimited for safety
# Verify: list all gzipped files created today
find /var/log/app -name "*.gz" -mtime -1
Why this works

BSD find on macOS differs from GNU find on Linux. Key gaps:

  • -mtime rounding: GNU rounds to the nearest 24-hour boundary; BSD is strictly 24 hours × N.
  • -exec {} + (batch mode) is a GNU extension — macOS BSD find does not support it. Use -print0 | xargs -0 instead for cross-platform scripts.
  • GNU find supports -printf for custom output format; BSD does not. Use stat as a workaround on macOS.

If you write scripts that must run on both, test on macOS explicitly or use gfind (GNU findutils via Homebrew).

Common mistake

Forgetting to quote the -name pattern. If the current directory happens to contain a file matching the glob, the shell expands it before find sees the -name argument:

# Imagine the current dir has error.log
find /var/log -name *.log    # shell expands to: find /var/log -name error.log

Now find searches only for a file named error.log — not all .log files. This bug is intermittent and hard to spot. Always quote -name and -path arguments: find /var/log -name "*.log".

Check yourself
Quiz

Which find command deletes all .tmp files under /tmp, safely handling filenames that contain spaces?

Recap

find <path> <predicates> traverses a directory tree and prints (or acts on) every entry that matches all predicates. Key predicates: -name (filename glob — always quote it), -type f/d/l (entry type), -size +N/-N (file size), -mtime +N/-N (modification time in days). -exec command {} \; runs the command per file; -exec command {} + batches all matches into one call (faster, but not available on BSD find). -print0 | xargs -0 is the safest pipeline when filenames may contain spaces.

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 4 done

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.