Loops that repeat work
Three ways to repeat: for over a list of words, while with a counter, and the brace range that writes a run of numbers for you.
For each thing in a list
The for loop walks a list of words and puts one of them into a name at a time. The list can be written out, produced by a range, or later handed over by another command. The block between do and done runs once per word. The name after for is created for the loop, and each pass gives it the next word.
A brace range is shorthand for a run: {1..5} becomes the five numbers, {a..c} becomes the three letters, and a third number sets the step, as in {2..10..2}. It is the shortest way to say "the numbers from here to there". The braces are expanded before the loop starts, so the range is fixed before the first pass.
While something is still true
A while loop repeats as long as its test stays true, so it needs something inside the block that eventually makes the test false. The usual pattern is a counter: start it at one, add to it each time round, and stop when it passes a limit. A counter is the simplest thing that changes: start it, compare it, move it.
Two words give you control from inside the block. break leaves the loop at once, which is how you stop early once you have found what you were looking for. continue skips the rest of this turn and moves to the next one. Both are worth using on purpose rather than by accident, because a loop that never ends is the one shell mistake that will sit there rather than complain. Both words act on the nearest loop, which matters as soon as loops are nested.
Loops, one after another
# A list written out.
for fruit in apple banana cherry; do
echo "fruit: $fruit"
done
# A range of numbers, and one with a step of two.
for i in {1..3}; do
echo "step $i"
done
for even in {2..6..2}; do
echo "even: $even"
done
# A counter, counted by the loop itself.
n=3
while [ "$n" -gt 0 ]; do
echo "countdown $n"
n=$((n - 1))
doneWhat a for loop really walks
The words after in are expanded before the loop begins, then handed over one at a time. That list can be written out, produced by a brace range, produced by a command substitution, or produced by a glob such as *.txt. The loop does not know where the words came from, only that there is a next one. The loop does not know whether the list came from a file, a range or another command.
Quoting changes the list. for f in "$@" keeps each argument whole, while for f in $@ splits every argument on its spaces. A glob that matches nothing stays as the literal pattern *.txt unless nullglob is set, which is why a loop can run once over a file name that does not exist. Quoting the expansion is what decides whether one item or several arrive.
Controlling a loop from inside
continue ends the current turn and asks for the next word; break leaves the loop entirely. Both act on the nearest enclosing loop, so inside two nested loops a break exits only the inner one and the outer loop carries on with its next value. A break in an inner loop leaves only that loop; the outer one continues.
A while loop is the same shape with a test instead of a list. The test is re-run after every turn, so something in the body has to move a value it reads; if nothing does, the loop is infinite. until is the same thing with the answer reversed. The test is asked again after every turn, so a value that never changes keeps the same answer.
Reading a loop before you run it
Before running a loop, say out loud what one pass does and what makes the next pass different. If the answer is "nothing", the loop will either run once or never stop. If the answer is "the counter goes up", check where it goes up and whether the test reads that same counter.
For a for loop, the list is decided before the first pass, so changing the list inside the body does not add or remove turns. That is a feature: the loop has a fixed shape, and the body cannot extend it by accident. A while loop has no such guarantee, which is why it needs the change written inside it.
A list, a counter and a range
Run it and match each block of output to the loop that wrote it. Then add a loop over three of your own words, and change the range so it counts down from five to one.
A while loop whose test never turns false runs forever. Before you read the output, check the block for the line that changes the value the test is watching.
Three traps: a while whose body never changes the tested value, {1..$n} expecting the range to use a variable — brace expansion happens before the variable is read, so use a counter or seq — and unquoted argument expansion splitting one value into several.
Try these next
- Loop over three words and skip one with
continue. - Count from one to five with a
whileloop, then rewrite it asfor i in {1..5}. - Break out of a loop as soon as a value matches, and print the value that stopped it.
Skip, stop, and count
Run it and follow which words are skipped and which one stops the loop. Then change the target word and add a second skip.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
Which pair of words wraps the body of a loop?
