Skip to content
Lesson 7 of 14

Step 1 of 5 · Reading · ~3 min

Learn

Control Flow

Loops

By the end of this lesson you can repeat commands over a list of words, a range of numbers, or a condition, read input line by line, and avoid the one famous trap that appears when the input is lines rather than words. Bash loops iterate over the things shells care about: words, lines, files and number ranges. The syntax is small. The skill is choosing what to feed the loop.

for: over a list of words

for color in red green blue; do
    echo "paint it $color"
done

for x in LIST walks the words after in. Those words usually come from an expansion:

for f in *.log; do          # a glob: every .log file, names with spaces intact
    echo "checking $f"
done

for i in {1..4}; do         # brace expansion: 1 2 3 4, inclusive at both ends
    echo "round $i"
done

Brace expansion is done before variables are substituted, so {1..$n} is the literal text {1..$n}, which is a silent bug. When the bound is a variable, use seq or the C-style loop:

for i in $(seq 1 "$n"); do      # seq prints the numbers, the loop splits them
    echo "$i"
done

for (( i = 1; i <= n; i++ )); do   # C-style: arithmetic rules apply inside
    echo "$i"
done

while: a condition, or the lines of input

while runs a command and repeats the body as long as it succeeds. With an arithmetic condition it is the loop for "until something changes":

size=1000
halvings=0
while (( size >= 10 )); do
    size=$(( size / 2 ))
    halvings=$(( halvings + 1 ))
done
echo "$halvings"      # 7: 1000, 500, 250, 125, 62, 31, 15, then 7 is below 10

Whatever the condition looks at must change inside the body, or the loop never ends. Check the first and last passes by hand: does the body run when the condition is already false, and does it stop on the right value?

The other everyday job for while is reading input line by line:

while read -r line; do
    echo "got: $line"
done

read succeeds while there is a line to read and fails at the end of input, so the loop consumes all of stdin. Write -r always. The tempting alternative, for line in $(cat file), is wrong: the unquoted expansion splits on all whitespace, so a line with spaces becomes several iterations. Lines want while read -r; words want for.

break leaves the loop and continue jumps to the next iteration. An endless loop is while true; do ... done, where true is just a command that always succeeds.

Common mistakes

  • Off by one. i < n stops one short of i <= n. Trace the loop for the smallest input.
  • Updating the wrong variable. A while whose body never changes what its condition tests runs forever.
  • {1..$n}. Braces expand before variables, so the loop body runs once with the text {1..5}.
  • Splitting lines on spaces. for line in $(cat file) iterates over words, not lines.
  • Printing inside the loop. An echo of the running total inside the body prints one line per iteration instead of one answer at the end.

Your exercise

Follow a number through a rule that is applied repeatedly until it reaches 1, counting the applications and tracking the largest value met on the way. The number of repetitions is not known in advance, which is what while is for. The starter prints both results; the loop that produces them is yours. Check input 1, which needs no steps at all.

Up nextFunctionsFunctions and Pipes

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…