Loops & Iteration
for Loops
Loop Over a Word List
The classic loop walks a list of words. Quote array expansions to keep elements intact.
for fruit in apple banana cherry; do
echo "fruit: $fruit"
done
servers=(web1 web2 db1)
for s in "${servers[@]}"; do
ping -c1 "$s" >/dev/null && echo "$s up" || echo "$s down"
done
C-Style Loop
When you need a counter, the arithmetic for reads like C — but note the double parentheses and the lack of $ inside them.
for ((i = 0; i < 5; i++)); do
printf 'i=%d squared=%d\n' "$i" "$((i * i))"
done
Ranges and Brace Expansion
for n in {1..5}; do echo "$n"; done # 1 2 3 4 5
for n in {10..1..2}; do echo "$n"; done # 10 8 6 4 2 (step -2)
for c in {a..e}; do echo "$c"; done
{1..5} is expanded before the loop runs, so it builds the whole list in memory. For huge ranges prefer the C-style loop.
Globbing Loop
shopt -s nullglob
for f in *.log; do
echo "processing $f"
done
while Loops
Reading a File Line by Line
This is the single most important loop idiom in shell scripting. Note the three essential details: IFS=, read -r, and the redirection on done.
while IFS= read -r line; do
printf 'line: %s\n' "$line"
done < data.txt
IFS=stops trimming leading/trailing whitespace.read -rstops the backslash from being eaten.- The redirection on
donekeeps the loop in the current shell.
cat file | while read … runs the loop in a subshell. Variables set inside are lost. Redirect from the file instead: while … ; done < file.
Counters and Conditions
n=0
while (( n < 3 )); do
echo "attempt $n"
(( n++ ))
done
while [[ -n "$pending" ]]; do
process "$pending"
pending=""
done
Infinite Loops
while true; do
if ! check_service; then
restart_service
fi
sleep 30
done
until Loops
until is the mirror image of while: it loops as long as the condition is false.
until curl -sf http://localhost:8080/health >/dev/null; do
echo "waiting for service..."
sleep 2
done
echo "service is up"
select Menus
select builds a numbered menu automatically and loops until you break. It reads from stdin, so it is for interactive scripts only.
select choice in "List files" "Show date" "Quit"; do
case "$choice" in
"List files") ls ;;
"Show date") date ;;
"Quit") break ;;
*) echo "invalid option" ;;
esac
done
break & continue
for f in *.txt; do
[[ -s "$f" ]] || continue # skip empty files
grep -q ERROR "$f" && { echo "fatal: $f"; break; }
echo "checked $f"
done
for i in {1..3}; do
for j in {1..3}; do
(( i == j )) && continue 2 # '2' skips the outer loop iteration
echo "$i-$j"
done
done
The optional number after break or continue chooses how many loop levels to affect.
Pitfalls
- Unquoted
$varin aforlist splits on spaces — use an array and"${arr[@]}". - Piping into
whileloses variables to the subshell — redirect instead. - Missing
-ronreadmangles Windows paths such asC:\temp. for i in $(ls)breaks on filenames with spaces; loop over a glob instead.