Loops repeat work so you do not. Bash has three — for walks a list, while runs while a command succeeds, until the inverse — and one safety habit ("$@"-style quoting) keeps all of them from breaking on filenames with spaces.
for: walking a list #
$ for svc in nginx redis aospix; do systemctl status "$svc"; done
$ for f in *.log; do gzip -9 "$f"; done
The glob form is the everyday hero — the shell expands the list, the loop handles each name. Quote the variable inside, always: unquoted "$f" breaks on spaces, per the quoting rules.
The C-style form exists for counters:
$ for ((i = 1; i <= 5; i++)); do echo "attempt $i"; done
while: driven by commands #
while runs as long as its command exits 0. The canonical pattern reads a file line by line:
$ while IFS= read -r line; do
echo "line: $line"
done < notes.txt
IFS= preserves leading whitespace, -r stops backslash mangling, and the redirect feeds the whole loop — not a command inside it. Piping into the loop works too, with the caveat that the loop then runs in a subshell, so variables set inside vanish after.
| Loop | Use when |
|---|---|
for x in list | Known list — literal, glob, or array |
while read -r line | Line-by-line over a file or stream |
while cmd | Repeat while a check passes |
until cmd | Repeat until a check passes |
for ((i=0;...)) | Counting |
until and the retry #
until inverts while — the natural shape of "wait for it":
$ until curl -sf http://localhost:8080/health > /dev/null; do
echo "waiting..." && sleep 2
done
Pair it with a timeout guard so a dead service yields a script that exits rather than one that waits forever:
$ for i in {1..30}; do curl -sf ... && break || sleep 2; done
Lists belong in arrays; dispatching inside a loop is case work. The scripting series continues.
