Computers are good at doing the same thing many times without getting bored or making slips. A loop is a statement that runs a block of statements repeatedly. Bash has two kinds you will use all the time: for, which repeats once for each value in a list, and while, which repeats as long as a condition is true.
for: once for each value in a list
for name in Ada Grace Alan; do
echo "Hello, $name!"
doneThis prints:
Hello, Ada!
Hello, Grace!
Hello, Alan!Read the pieces in order:
for name innames a variable, herename, called the loop variable.Ada Grace Alanis the list of values, separated by spaces.; dostarts the block anddoneends it. The block between them is called the body of the loop.
Bash sets name to the first value and runs the body, then sets it to the second value and runs the body again, and so on. When the list is used up, the script continues after done. As with if, indent the body so that a reader can see what is repeated.
Counting with a range
To repeat a fixed number of times, let Bash write the list of numbers. {1..5} is replaced by 1 2 3 4 5.
for i in {1..5}; do
echo "Lap $i"
done
echo "Finished"This prints Lap 1 to Lap 5, one per line, and then Finished. A variable that counts the rounds of a loop is called a counter, and i is the traditional short name for one. Finished is printed once, because that line is after done and so is not part of the body.
Running totals
A loop becomes powerful when a variable is carried from one round to the next. To add up numbers, start a total at 0 before the loop and add to it inside:
total=0
for n in 4 5 6; do
total=$(( total + n ))
done
echo "Total: $total"This prints Total: 15. Step through the diagram to watch total grow.
total starts at 0 before the loop. Each round adds the current value of n to it. After the last round the loop ends and line 5 prints the result.Where the starting line goes matters. If total=0 were inside the body, the total would be set back to 0 at the start of every round.
while: repeat as long as a condition is true
A while loop has a condition, written exactly like the condition of an if. Bash tests it; if it is true, the body runs and Bash goes back and tests again. When the condition is false, the loop ends.
count=3
while [[ "$count" -gt 0 ]]; do
echo "$count"
count=$(( count - 1 ))
done
echo "Lift off!"This prints 3, 2, 1 and then Lift off!.
done.The last line of the body, count=$(( count - 1 )), is what makes the loop end. Something in the body must change the value that the condition tests. If you delete that line, count stays 3, the condition stays true and the loop prints 3 for ever. A loop that never ends is called an infinite loop. On this site a script is stopped after a few seconds; at a terminal, hold the Ctrl key and press C to stop one.
Counting up to a number held in a variable
Often the number of rounds is not known when you write the script: the user types it. The range form cannot do this. {1..$n} is not replaced by a list of numbers:
n=3
for i in {1..$n}; do
echo "Lap $i"
doneThis prints the single line Lap {1..3}. Bash turns ranges into lists before it replaces variables, so at that moment it sees $n where a number should be and leaves the text alone. There is no error message.
A while loop with a counter does the job. Start the counter at 1, repeat while it is less than or equal to the limit, and add 1 at the end of each round:
n=3
i=1
while [[ "$i" -le "$n" ]]; do
echo "Lap $i"
i=$(( i + 1 ))
doneThis prints Lap 1, Lap 2 and Lap 3.
Counting is so common that for has a second form that puts the three counter parts on one line:
n=3
for (( i = 1; i <= n; i++ )); do
echo "Lap $i"
doneThis prints the same three lines. Between the double round brackets are three parts separated by semicolons: where the counter starts (i = 1), the condition for another round (i <= n), and what to do after each round (i++, which adds 1 to i). These brackets follow the rules of $(( )): spaces are allowed, names need no $, and numbers are compared with <, <=, > and >=. Use whichever of the two forms you find clearer.
Off-by-one mistakes
The most common loop mistake is running one round too few or one too many. It is called an off-by-one mistake, and it comes from the choice between -lt and -le and from where the counter starts.
i=1
while [[ "$i" -lt 3 ]]; do
echo "Lap $i"
i=$(( i + 1 ))
doneThis prints only Lap 1 and Lap 2: when i reaches 3, "3 is less than 3" is false. For three laps the test must be -le 3. When a loop is wrong by one, check the first round and the last round by hand: what is the counter the first time the body runs, and what is it the last time the test is true?
Repeating until the user says stop
A condition can depend on what the user types. This script adds up numbers until the user types 0:
total=0
echo "Number? (0 to stop)"
read -r number
while [[ "$number" -ne 0 ]]; do
total=$(( total + number ))
echo "Number? (0 to stop)"
read -r number
done
echo "Total: $total"With 5, 7 and 0 typed, it asks three times and prints Total: 12. Notice the shape: read once before the loop so that the condition has something to test, and read again at the end of the body so that the next test sees a new value. Try it in the Bash playground with one number per line of Standard input, ending with 0.
Error messages you will meet
A missing done.
for i in {1..3}; do
echo "Lap $i"solution.sh: line 3: syntax error: unexpected end of file from `for' command on line 1This is the same message as a missing fi. The second line number, line 1, points at the loop that was never closed.
A missing do.
i=1
while [[ "$i" -le 3 ]]
echo "Lap $i"
i=$(( i + 1 ))
donesolution.sh: line 5: syntax error near unexpected token `done'
solution.sh: line 5: `done'Bash met done while it was still waiting for do. As with a missing then, the named line is fine and the mistake is above it: line 2 needs ; do at its end.
What you can do now
You can repeat a block for each value in a list or range, repeat while a condition holds, keep a counter and a running total, and check the first and last rounds for off-by-one mistakes. In the exercises you will predict what loops print, then write a script that adds up as many numbers as the user asks for.