====== 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
* ''-0'' pairs with ''-print0'': spaces/newlines in names stay safe. Use it.
* ''-P4'' parallelism: on this dual-core, ''-P2'' to ''-P4'' is about right
(more just context-switches).
* ''-I{}'' implies ''-n1''; combine carefully.
* ''xargs'' with no input still runs the command once: ''-r''/''--no-run-if-empty''
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 =====
* [[terminal:scripting:start|Scripting Index]]
* [[terminal:tools:start|CLI Tools]]
* [[terminal:start|Return to terminal]]
* [[start|Return to wiki home]]