Why this matters
A for loop needs its list up front. Often you do not have one: you want to keep going until something happens. Retry the download until it works (but at most five times). Wait until a file appears. Keep reading lines while there are lines. Ask again until the user types a valid answer. That is the job of while and until.
while runs a command, again and again
while COMMANDS; do
BODY
done
Like if, while runs a command and checks its exit status. If it is 0, the body runs, and then the condition is run again. The loop ends the first time the condition fails. Any command works as the condition: [[ ]], (( )), grep -q, read, ping, a function.
n=1
while (( n <= 3 )); do
echo "round $n"
(( n += 1 ))
done
echo "done, n is now $n"
round 1
round 2
round 3
done, n is now 4
Three ingredients make a counting loop: initialize the counter before the loop, test it in the condition, update it in the body. Forget the update and the loop never ends. (Press Ctrl-C to stop a runaway loop in the shell; the checker stops it after a few seconds.)
until is while inverted
until runs the body as long as the condition fails:
until [[ -f /work/ready.flag ]]; do
echo "waiting for ready.flag..."
sleep 1
done
echo "it is there"
until C is exactly while ! C. Use whichever reads better. sleep 1 pauses for one second (the kernel suspends the process, using no CPU); sleep 0.2 works for fractions.
Infinite loops with a way out
Sometimes the exit condition is easiest to check in the middle of the body. Then loop forever and leave with break:
while true; do
read -r -p "Continue? [y/n] " answer
case $answer in
y) echo "continuing"; break ;;
n) echo "stopping"; exit 1 ;;
*) echo "please type y or n" ;;
esac
done
true always succeeds, so only break (leave the loop) or exit (leave the script) end it. read -p prints a prompt first. : is another spelling of true, so you will also see while :; do.
Retry with a limit
The pattern you will write most often in real scripts:
#!/bin/bash
# fetch.sh: try a command up to 5 times, waiting longer each time
max=5
delay=1
attempt=1
until some_command; do
if (( attempt >= max )); then
echo "giving up after $attempt attempts" >&2
exit 1
fi
echo "attempt $attempt failed; retrying in ${delay}s" >&2
sleep "$delay"
(( attempt += 1, delay *= 2 ))
done
echo "succeeded on attempt $attempt"
Each failure doubles the wait (1, 2, 4, 8 seconds), a technique called exponential backoff. It gives an overloaded service time to recover instead of hammering it.
Reading until the input ends
read returns failure when there is nothing more to read (end of file). That makes it a natural while condition:
total=0
while read -r n; do
(( total += n ))
done
echo "sum: $total"
$ printf '3\n4\n5\n' | bash sum.sh
sum: 12
-r stops read from treating backslashes specially; always use it. The next lesson is all about reading lines and fields safely, including the edge cases (lines with leading spaces, a last line without a newline).
What happens underneath
Each time round, bash runs the condition. If the condition is a builtin or keyword ([[, ((, read, true) this is cheap: no process is created. If it is an external program, like grep -q pattern file or ping -c1 host, bash forks and execs it on every iteration and waits for its status. A while loop that polls an external command many times a second will keep a CPU busy; that is why polling loops include a sleep.
Common mistakes
- Forgetting to update the counter. Endless loop. The checker reports
timed out: endless loop, or waiting for input?. - An off-by-one condition.
while (( n < 3 ))starting at 1 runs twice, not three times. Trace the first and last rounds by hand. - Expecting
whileto test a value.while $runningruns the contents of$runningas a command. That is fine if it holdstrueorfalse; anything else givescommand not found. - Polling without sleeping.
until [[ -f x ]]; do :; doneburns a whole CPU core. - Using
(( count++ ))underset -e. With count at 0 the status is 1 (lesson 9.3). Use(( count += 1 )). - Missing
do.syntax error near unexpected token 'done'.
Exercises
collatz is a classic while loop where you cannot know the number of rounds in advance; it is checked with 0 (which must not loop forever) and 27 (111 steps). guess reads from standard input and is tested with input that runs out. The retry task gives you a deliberately flaky service; your script must stop retrying the moment it succeeds.
Recap
while C; do ...; donerepeats while command C succeeds;untilrepeats while it fails.- Counting loops need initialize, test and update.
while trueplusbreakorexitputs the exit test in the middle.- Retry loops: a counter, a limit, a
sleep, ideally growing (backoff). while read -r linereads input until it ends.- External commands in a condition are forked every round; add a
sleepwhen polling.
Going further: help while, help until, help read. Try timeout 3 bash -c 'while :; do :; done' and watch it in another terminal with top: that is what a loop without sleep costs.