Build the mental model
A `for` loop repeats over a fixed list of items known (or computable) in advance — words you write out literally, a glob pattern like `*.log` that the shell expands into matching filenames before the loop even starts, or the classic C-style `for ((i=0; i<n; i++))` when what you actually need is a numeric counter rather than a list of words. A `while` loop instead keeps re-evaluating its condition and running as long as that condition stays true, which makes it the natural choice whenever the number of iterations isn't known ahead of time — reading input line by line with `while read`, or repeating some action until a piece of state changes. `until` is `while`'s mirror image, running as long as its condition is false rather than true; it exists purely for readability, for the cases where a loop reads more naturally as 'keep going until X happens' than as 'keep going while X hasn't happened.' Inside any of these three, `break` exits the loop immediately regardless of the condition, and `continue` skips the rest of the current iteration's body and jumps straight to the next one. One subtlety worth knowing early, because it trips up almost everyone the first time: piping a command into a `while read` loop (`cmd | while read ...`) runs the entire loop in a subshell, since a pipeline forks a separate process for each stage — so any variable the loop modifies, like a running counter, reverts to its pre-loop value the moment the loop finishes, a gotcha the lesson's own example demonstrates directly.
Connect it to a real scenario
Processing a log file line by line to count how many lines match a pattern is a textbook `while read` use case: `while IFS= read -r line; do ...; done < file.txt` reads each line safely — including ones with leading or trailing spaces, which `IFS=` preserves — without the word-splitting surprises a naive approach would hit, and it's exactly what the lesson's example does when it reads `lines.txt` and increments a `total` counter to 3. Meanwhile, a script that needs to retry a flaky network call up to five times is a natural fit for a C-style `for` loop combined with `break` — stop looping as soon as the call succeeds instead of always running all five attempts. The example's final block makes the subshell gotcha concrete: piping `printf` output into `while read` increments `count` to 3 inside the loop, but because that loop ran in a subshell forked for the pipeline, the outer `count` is still 0 once the pipeline finishes — exactly the kind of surprise that redirecting from a file (`< file.txt`) instead of piping avoids.
Try the working example
#!/usr/bin/env bash
echo "for over a list:"
for color in red green blue; do
echo " color -> $color"
done
echo "for over a glob:"
mkdir -p /tmp/bash-lesson-loops
touch /tmp/bash-lesson-loops/a.log /tmp/bash-lesson-loops/b.log
for f in /tmp/bash-lesson-loops/*.log; do
echo " found -> $(basename "$f")"
done
echo "C-style for:"
for ((i = 1; i <= 3; i++)); do
echo " i -> $i"
done
echo "while loop:"
n=3
while [ "$n" -gt 0 ]; do
echo " countdown -> $n"
n=$((n - 1))
done
echo "until loop:"
m=1
until [ "$m" -gt 3 ]; do
echo " climbing -> $m"
m=$((m + 1))
done
echo "break and continue:"
for num in 1 2 3 4 5; do
if [ "$num" -eq 2 ]; then
continue
fi
if [ "$num" -eq 4 ]; then
break
fi
echo " num -> $num"
done
echo "while read from a file:"
printf 'line one\nline two\nline three\n' > /tmp/bash-lesson-loops/lines.txt
total=0
while IFS= read -r line; do
echo " read -> $line"
total=$((total + 1))
done < /tmp/bash-lesson-loops/lines.txt
echo "total lines counted: $total"
echo "the subshell gotcha (piped while read):"
count=0
printf 'x\ny\nz\n' | while IFS= read -r _; do
count=$((count + 1))
done
echo "count after piped loop: $count"
rm -rf /tmp/bash-lesson-loopsRunning this script prints:
for over a list:
color -> red
color -> green
color -> blue
for over a glob:
found -> a.log
found -> b.log
C-style for:
i -> 1
i -> 2
i -> 3
while loop:
countdown -> 3
countdown -> 2
countdown -> 1
until loop:
climbing -> 1
climbing -> 2
climbing -> 3
break and continue:
num -> 1
num -> 3
while read from a file:
read -> line one
read -> line two
read -> line three
total lines counted: 3
the subshell gotcha (piped while read):
count after piped loop: 05-minute try-it
Write a script that creates three files named task1.txt, task2.txt, task3.txt, loops over them with a for-loop glob to print each filename, then separately uses a while loop with a counter to print a countdown from 5 to 1.
One important caution
Piping a command into while read (cmd | while read ...) and then being confused why a counter incremented inside the loop is back to its original value afterward — the pipe runs the loop in a subshell
Forgetting -r on read, so backslashes in the input get interpreted/stripped instead of being read literally
GNU Bash Reference Manual — Looping Constructs — Bash / Shell Scripting