Skip to content
find and xargs
step 1/7

Reading — step 1 of 7

Learn

~3 min readI/O and Iteration

For batch operations on files, find + xargs is the canonical Unix pattern. Bash Cookbook treats these as core daily-use tools.

find — locate files

# Find all .txt files under current dir:
find . -name '*.txt'

# By type:
find . -type f                 # regular files only
find . -type d                 # directories only
find . -type l                 # symlinks

# By age:
find . -mtime -7               # modified within last 7 days
find . -mtime +30              # modified more than 30 days ago

# By size:
find . -size +100M             # over 100 megabytes
find . -size -1k               # under 1 kilobyte

# Combine:
find /var/log -type f -name '*.log' -mtime +30

Acting on results — -exec

find . -name '*.tmp' -delete                 # built-in: delete
find . -name '*.tmp' -exec rm {} \;          # generic: run rm per file
find . -name '*.tmp' -exec rm {} +           # batched: rm with many args (faster)
  • {} is replaced with each filename
  • \; runs once per file
  • + batches into one invocation (orders of magnitude faster on many files)

xargs — pipe into a command

When you need filenames piped in:

find . -name '*.txt' | xargs wc -l           # count lines in all .txt

But filenames with SPACES break this. Use null-separation:

find . -name '*.txt' -print0 | xargs -0 wc -l
  • -print0 — separate filenames with NUL bytes
  • -0 — xargs reads NUL-separated input

Always use -print0 + -0 for production scripts. Filenames may have spaces, newlines, anything.

xargs with parallelism

find . -name '*.jpg' -print0 | xargs -0 -n1 -P4 mogrify -resize 50%
  • -n1 — one filename per command invocation
  • -P4 — run 4 in parallel

Great for embarrassingly-parallel work — image conversion, file compression, format conversion.

Composing find with grep

# Files containing a string:
find . -name '*.go' -exec grep -l 'TODO' {} +

# More common: just use grep -r:
grep -rl 'TODO' --include='*.go' .

For most cases, grep -r (recursive) is simpler than find + grep.

Pruning — skip directories

find . -path './node_modules' -prune -o -name '*.js' -print

The -prune skips matching paths. Useful for excluding .git, node_modules, vendor, dist, etc.

When NOT to use find

  • For simple recursive listing — ls -R or shell globs (**/*.txt with globstar enabled)
  • For matching file CONTENT — use grep -r directly
  • When output goes through complex pipelines — consider fd (a modern find replacement)

Common mistakes

  • Spaces in filenamesfind . | xargs is broken. Always -print0 | xargs -0.
  • Forgetting -type f — find returns directories too by default. -type f for files only.
  • -exec ... \; for many files — slow. Use + for batching.
  • Confusing -delete with -exec rm — both work; -delete is built-in and slightly safer (atomic, no shell escape issues).
  • Wildcards UNQUOTEDfind . -name *.txt expands the glob in the CURRENT directory before find sees it. Always quote: find . -name '*.txt'.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…