Bash until vs while loop: exit codes, same counter, and when to pick each

Bash until vs while using exit status: bash until loop runs while a command fails, while loop bash runs while it succeeds. Examples for counters, wait until ready, and while read line-by-line.

Published

Updated

Read time 4 min read

Reviewed byDeepak Prasad

Bash until vs while loop: exit codes, same counter, and when to pick each

In Bash, while and until are the same mechanism with the condition flipped. The test is always “run a command, look at its exit status”: 0 means success, non-zero means failure. A while loop runs the body while the command succeeds; an until loop runs the body until the command succeeds (that is, while it still fails). Anything you write with one can be written with the other by negating the test—use whichever form matches how you read the condition aloud.

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

For more patterns (infinite loops, nested loops, pitfalls), see bash while loop.


Bash until vs while: exit status rules

Loop Body runs when the condition command…
while CMD exits 0 (true / success)
until CMD exits non-zero (false / failure)

So while true loops forever, and until false does the same. until true runs the body zero times because the condition is already successful before the first iteration:

bash
while true; do echo one; break; done

until true; do echo never; done
echo after_until_true

Output:

text
one
after_until_true

Equivalence you can rely on in reviews and refactors:

  • while CMD behaves like until ! CMD
  • until CMD behaves like while ! CMD

(Here ! is shell negation of the exit status, not arithmetic.)


While do loop bash and bash do until: syntax

Order matters: it is while CONDITION; do, not “while do” first—the keyword do opens the body, and done closes it. until is the same shape.

bash
while CONDITION; do
  # body
done

until CONDITION; do
  # body
done

CONDITION can be any command or pipeline Bash allows in that position; in practice most scripts use [[ … ]] for Bash-only hosts, or [ … ] when you care about POSIX sh. Details live in the Bash manual on looping constructs.


Bash until loop vs bash while loop: same counter

Count from 0 to 4 with while: keep going while the value is still strictly less than 5.

bash
i=0
while [[ "$i" -lt 5 ]]; do
  echo "i = $i"
  i=$((i + 1))
done

The until version keeps going while “greater or equal than 5” is false, so it stops once i reaches 5:

bash
i=0
until [[ "$i" -ge 5 ]]; do
  echo "i = $i"
  i=$((i + 1))
done

Output (either script):

text
i = 0
i = 1
i = 2
i = 3
i = 4

You could also negate a while-style test inside until (for example until [[ ! "$i" -lt 5 ]]), but that is usually harder to read than picking a direct comparison such as -ge.


Bash until: wait until something becomes true

Retries—“bash loop until the service answers”, “until bash sees the file”—are often clearer as until, because the thing you care about is success of one command, and you want to keep trying until that command finally exits 0.

Self-contained example: wait until a file your deploy script expects shows up (here a background subshell creates it shortly after start):

bash
path=$(mktemp -u)
(sleep 0.12; : >"$path") &
until [[ -f "$path" ]]; do
  sleep 0.02
done
echo "file appeared"
rm -f "$path"
wait

Output:

text
file appeared

If you prefer while, negate the test: while ! [[ -f "$path" ]]; do …. Same behavior; choose the line that matches how you describe the problem out loud.

For TCP ports, many people keep a until nc -z host port (or while ! nc -z …) loop; that needs nc installed and something actually listening, so treat it as the same pattern as the file wait with a different condition command.


While read: line-by-line input

Reading lines with read succeeds until EOF. The usual pattern is while … read; you can write an until ! read variant, but it is harder to read for most people.

bash
f=$(mktemp)
printf 'a\nb\nc\n' >"$f"

num_lines=0
while IFS= read -r line || [[ -n $line ]]; do
  num_lines=$((num_lines + 1))
done <"$f"

echo "lines=$num_lines"
rm -f "$f"

Output:

text
lines=3

Use IFS= read -r so leading or trailing whitespace is not trimmed and backslashes are not treated as escapes. The || [[ -n $line ]] guard handles a last line that has no trailing newline.


Summary

while keeps going while the condition command exits 0; until keeps going while it exits non-zero, and stops once it succeeds. You can always rewrite one form as the other by negating the test (while ! … vs until …). Use until when the job is “retry until this check passes,” while when it is “keep going while this stays true,” and while read when you process a file line by line.

When the stop condition is a wall-clock deadline (for example “run until 17:00” or “run for five minutes”), run a while loop until a specific time in Bash collects the usual sleep / date / timeout patterns.

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 …