Table of Contents

find and xargs

find predicates: filter by anything

find dir -name '*.txt'           # glob on name (-iname case-insensitive)
find dir -type f                 # f file, d dir, l symlink, s socket
find dir -size +100M             # >100M (also -10k, =size, c=M)
find dir -mtime -7               # modified <7 days ago (+7 = older)
find dir -newermt '2026-01-01'   # date string version
find dir -newer ref.txt          # newer than a file
find dir -perm -644              # at least these bits
find dir -user brennan -group staff
find dir -empty                  # empty files/dirs
find dir -readable ! -writable

Combining & depth

find . -name '*.log' -o -name '*.tmp'      # OR: group it: \( a -o b \)
find . -name '*.log' -a -size +1M          # AND (implicit -a)
find . ! -name '*.md'                      # NOT
find . -maxdepth 2 -type f                 # depth limit
find . -path '*/node_modules' -prune -o -name '*.js' -print   # skip subtree
find . -regex '.*\.(jpg|png)'              # full-path regex (-regextype posix-ext)

Acting on results

find . -name '*.tmp' -delete                       # careful: no undo
find . -name '*.sh' -exec chmod +x {} \;           # run per file
find . -name '*.sh' -exec chmod +x {} +            # batched: much faster
find . -name '*.bak' -exec mv {} archive/ \;       # {} = filename
find . -type f -exec grep -l 'secret' {} +
find . -newer marker -print0 | xargs -0 rm         # safe for weird names

-exec cmd {} ; runs once per file. -exec cmd {} + batches like xargs : prefer + on a 2-core machine (one process spawn, not thousands).

`-printf`: custom output

find . -type f -printf '%s %p\n' | sort -rn | head    # biggest files
find . -type f -printf '%T+ %p\n' | sort | tail       # most recent
find . -type d -empty -printf '%p\n'

xargs: feed a list to a command

find . -name '*.o' | xargs rm                    # rm a b c … batched
cat urls.txt | xargs -n1 curl -sO                # one arg per call
echo a b c | xargs -n1 -I{} echo "item-{}"       # {} placeholder
find . -name '*.mp3' -print0 | xargs -0 -n1 -P4 loudnorm   # parallel ×4
printf '%s\0' *.png | xargs -0 -n5 echo          # 5 per invocation

(more just context-switches).

prevents that.

GNU parallel (if you want more)

sudo apt install parallel
find . -name '*.jpg' | parallel convert {} {.}.webp
parallel -j4 ffmpeg -i {} {.}.mp3 ::: *.wav

Recipes

# bulk rename extensions
find . -name '*.jpeg' -exec bash -c 'mv "$1" "${1%.jpeg}.jpg"' _ {} \;
 
# files modified today, copied out
find ~ -type f -mtime -1 -print0 | tar --null -T - -cf ~/changed.tar
 
# count lines across a tree
find src -name '*.sh' -exec cat {} + | wc -l
 
# dry-run discipline: always preview before -delete/-exec rm
find . -name '*.tmp' -print        # look first, then swap -print → -delete

See Also