Bash while loop: syntax, `while true`, `read`, `break`, and “do while” in Bash

Bash while loops: `while`/`do`/`done`, any command as the condition, `while true` with `break` and `continue`, `while read` over files, do-while style with `while true`, nested loops, and `break` vs `exit`.

Published

Updated

Read time 6 min read

Reviewed byDeepak Prasad

Bash while loop: syntax, `while true`, `read`, `break`, and “do while” in Bash

A while loop runs its body while a command list succeeds (exit status 0). That command can be [[ … ]], (( … )), grep -q, read, or anything else that returns a status—the loop keeps going until that list ends with a non-zero status. Use while true when you want an open-ended loop and plan to stop with break, a signal, or an inner condition.

Tested all the commands and code from this article on Ubuntu 25.04, kernel 6.14.0-37-generic, Bash 5.2.37.

For numeric tests, see bash compare numbers. For the opposite polarity (until), see bash until vs while loop. For stepping through lists without read, see bash for loop.


Bash while syntax (whiledodone)

The usual form is one line plus do / done:

bash
while CONDITION_COMMANDS; do
  BODY_COMMANDS
done

You can also put while and do on separate lines; what matters is that the part between while and do is a normal command list whose last command’s exit status decides whether another iteration runs.

bash
a=1
while [[ $a -le 3 ]]; do
  echo "a=$a"
  a=$((a + 1))
done

Output:

text
a=1
a=2
a=3

Searchers sometimes type while do bash or bash loop while; in the shell the keyword order is always while …; do … done, not do while.


The loop condition can be any command

Any pipeline or compound command may appear after while. Bash re-runs that list before each iteration; 0 continues, non-zero stops.

Arithmetic form (handy for counters):

bash
n=0
while ((n < 3)); do
  echo "n=$n"
  ((n++))
done

Output:

text
n=0
n=1
n=2

Here ((n < 3)) returns 0 while the inequality holds, like a tiny expression language inside Bash.


Infinite loops (while true)

while true (or while :, since : is a built-in that always succeeds) loops until something inside the body stops execution—classically break, rarely return from a function, or an external signal (for interactive loops, you may also trap SIGINT; see capture Ctrl+C in bash if you need that).

bash
i=0
while true; do
  ((++i))
  echo "i=$i"
  ((i >= 3)) && break
done

Output:

text
i=1
i=2
i=3

Tight spinners (no sleep, no I/O wait) can burn CPU; add a short sleep or block on a real event when you poll.


break and continue

break leaves the innermost while/until/for/select immediately. continue skips the rest of the body and jumps to the next test.

bash
k=0
while ((k < 5)); do
  ((k++))
  ((k == 2)) && continue
  ((k == 5)) && break
  echo "k=$k"
done

Output:

text
k=1
k=3
k=4

Iteration 2 is silent because of continue; value 5 exits before the echo because of break.


break leaves the loop; exit stops the shell

break ends the loop; the script keeps running. exit ends the shell process—often the whole script—so lines after the loop never run.

bash
bash -c 'f() { while true; do break; done; echo after_loop; }; f; echo still_here'

Output:

text
after_loop
still_here
bash
bash -c 'f() { while true; do exit 0; done; echo never; }; f; echo still_here'

This second invocation prints nothing from f or the line after it: the exit terminates the bash -c subshell as soon as it runs.


Reading a file line by line (while read)

While loop in bash over text files almost always uses read, with IFS= and -r so you do not mangle whitespace or treat backslashes specially.

bash
f=$(mktemp)
printf 'one\ntwo\n' >"$f"

while IFS= read -r line || [[ -n $line ]]; do
  echo "read: $line"
done <"$f"

rm -f "$f"

Output:

text
read: one
read: two

The || [[ -n $line ]] part covers a last line that has no trailing newline.


Do-while style: run the body at least once

Some languages have a do { … } while (condition); form so the body always runs once before the condition is checked. Bash does not spell that out as its own keyword, but you can get the same behavior in two straightforward ways.

Option 1: while true and a break

Put the real work first, then decide whether to stop. Here the loop prints n starting at 0, then stops after n reaches 3:

bash
n=0
while true; do
  echo "n=$n"
  ((n++))
  ((n >= 3)) && break
done

Output:

text
n=0
n=1
n=2

The first iteration always runs because the only “condition” on the while line is the constant true.

Option 2: put read and the test in the while line

The part between while and do is a list of commands; Bash uses the exit status of the last command as the loop test. This loop reads a name, checks that it is non-empty, and only then runs the body:

bash
while
  read -r name
  [[ -n $name ]]
do
  echo "hello $name"
done <<'EOF'
alice
bob

EOF
echo done

Output:

text
hello alice
hello bob
done

What happens each time:

  1. read -r name takes the next line from the here-document.
  2. [[ -n $name ]] succeeds only if that line was not empty.
  3. If both succeed, the do block runs (echo).
  4. An empty line makes [[ -n $name ]] fail, so the loop stops before another echo. If the very first line were empty, the body would never run—so this is not the same as “always once.” For a hard guarantee that the body runs before any test, use Option 1.

Nested while loops

Inner loops run to completion for each outer iteration—same scoping rules as other languages; keep names clear so i / j do not blur together.

bash
o=1
while ((o <= 2)); do
  echo "outer $o"
  i=1
  while ((i <= 2)); do
    echo "  inner $i"
    ((i++))
  done
  ((o++))
done

Output:

text
outer 1
  inner 1
  inner 2
outer 2
  inner 1
  inner 2

Summary

A while loop keeps running its body while the command list before do ends with exit status 0. Use while true (or while :) when you want an open loop and plan to stop with break, return, or by exiting the process; add sleep or wait on real I/O when you poll so you do not busy-spin. continue skips the rest of the current iteration; break leaves only the loop, while exit ends the shell. For text files, while read with IFS= read -r is the usual line-by-line pattern. For a do-while effect (body at least once before a real test), use while true plus a conditional break, or combine read and [[ … ]] on the while line when that matches how your input arrives.

Further reading:

Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …