Quick Loops
you don’t need a whole script for a loop. most of the ones I run are one-liners right in the terminal.
the template
for f in *.txt; do SOMETHING "$f"; donethat’s it. replace SOMETHING and go.
file renaming
change extensions:
for f in *.txt; do mv "$f" "${f%.txt}.md"; donethe ${f%.txt} bit strips the suffix — see bash-variable-tricks if that looks weird.
add a prefix:
for f in *.jpg; do mv "$f" "thumb_$f"; donewith brace expansion
combine with bash-brace-expansion for generated lists:
for i in {1..10}; do echo "item $i"; done
for env in {dev,staging,prod}; do ./deploy.sh "$env"; doneserver names:
for h in web0{1..5}; do ssh "$h" uptime; done
# hits web01 through web05looping over command output
for pid in $(pgrep nginx); do echo "nginx: $pid"; doneworks fine for simple stuff. BUT if filenames have spaces this approach breaks. for that use the safe version:
while read (the safe way)
find . -name "*.log" | while read -r f; do
gzip "$f"
done-r prevents backslash weirdness. always use it with read.
real examples I actually use
check if sites are up:
for url in google.com github.com mysite.com; do
curl -so /dev/null -w "%{http_code} $url\n" "https://$url"
donefind and replace across files:
for f in *.html; do sed -i 's/old/new/g' "$f"; doneretry something until it works:
for i in {1..5}; do
curl -sf localhost:8080/health && break
echo "attempt $i failed"
sleep 2
donexargs as an alternative
sometimes cleaner than a loop, and -P gives you parallelism for free:
find . -name "*.tmp" | xargs rm
find . -name "*.jpg" | xargs -P4 -I{} convert {} -resize 50% {}see bash-process-tricks for more parallel stuff.
the footgun
don’t do for f in $(ls). it breaks on spaces, newlines, all kinds of stuff. just use for f in * or find with while read. this page explains why.
see also: bash-variable-tricks, bash-brace-expansion, bash-process-tricks, bash-keyboard-shortcuts
bashfaq on reading files — worth reading if you do a lot of text processing