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"; done

that’s it. replace SOMETHING and go.

file renaming

change extensions:

for f in *.txt; do mv "$f" "${f%.txt}.md"; done

the ${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"; done

with 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"; done

server names:

for h in web0{1..5}; do ssh "$h" uptime; done
# hits web01 through web05

looping over command output

for pid in $(pgrep nginx); do echo "nginx: $pid"; done

works 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"
done

find and replace across files:

for f in *.html; do sed -i 's/old/new/g' "$f"; done

retry something until it works:

for i in {1..5}; do
    curl -sf localhost:8080/health && break
    echo "attempt $i failed"
    sleep 2
done

xargs 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