Bash until Loop: Run Commands Until a Condition Becomes True
Learn Bash until loops: syntax, exit statuses, numeric tests, retries, file polling, timeouts, break, continue, and comparisons with while loops.
An until loop repeatedly runs a group of commands while its condition is false. As soon as the condition becomes true, Bash stops repeating the loop. This makes until useful for counters, retries, polling for files, and waiting for services.
The key rule is the opposite of a while loop:
whilecontinues when its condition is true.untilcontinues when its condition is false and stops when it is true.
For a complete beginner-oriented comparison, see the Bash until loop guide.
How an until Loop Works
Bash evaluates the loop condition before every iteration, including before the first iteration. If the condition returns a nonzero exit status, Bash runs the loop body. If it returns zero, Bash skips the body or stops repeating.
Because this is a pre-test loop, the body can run zero times. If the condition is already true before the loop starts, Bash immediately exits the loop.
until condition; do
commands
done
The condition is a command or test expression. The commands between do and done are the loop body.
| Component | Role | Notes |
|---|---|---|
until | Starts the loop structure | Repeats while the condition is false. |
| condition command | Determines whether to continue | Zero means true; nonzero means false. |
do | Marks the beginning of the body | May follow the condition on the same line after a semicolon. |
| loop body | Commands repeated by the loop | Usually changes the state tested by the condition. |
done | Ends the loop | Bash returns to the condition after the body finishes. |
Multiline and Same-Line Forms
The conventional multiline form places do on its own line:
until condition
do
commands
done
In normal Bash style, do is commonly written without the leading indentation shown above:
until condition
do
commands
done
It can also appear on the same line as the condition. The semicolon terminates the condition command before do:
until condition; do
commands
done
Exit Statuses and Conditions
A shell command returns a numeric exit status when it finishes. By convention:
- Exit status
0means success, which shell conditions interpret as true. - A nonzero exit status means failure, which shell conditions interpret as false.
An until loop runs its body when the condition command returns nonzero. It stops when that command returns zero.
until grep -q "ready" status.txt; do
printf '%s\n' 'The ready marker has not been found.'
sleep 1
done
Here, grep returns nonzero when it does not find ready, so the loop continues. When grep finds the text, it returns zero and the loop stops.
The [ ... ] Test Command
The bracket form is a command named [. It evaluates file, string, and numeric tests. The closing bracket is a required final argument, so spaces are mandatory:
until [ "$status" -eq 0 ]; do
commands
done
Do not write [$status -eq 0]. The missing spaces change the arguments and usually produce an error or an unexpected result. Quote variable expansions in bracket tests so an empty or whitespace-containing value does not break the expression.
| Test | Meaning | Suitable use in an until loop |
|---|---|---|
-eq and -ne | Numbers are equal or not equal | Stop when a counter reaches a specific value. |
-lt and -ge | Less than or greater than or equal | Continue below a limit or stop at a lower bound. |
-e | A path exists | Wait for any kind of file-system entry. |
-f | A regular file exists | Wait for a generated file or completion marker. |
-d | A directory exists | Wait for a directory to be created. |
| command exit status | A command succeeded or failed | Retry an operation until it succeeds. |
Arithmetic Conditions
Bash arithmetic evaluation uses (( ... )). It is convenient for numeric conditions and counters:
count=0
until (( count >= 5 )); do
printf 'count=%s\n' "$count"
((count++))
done
Inside (( ... )), a nonzero arithmetic result is treated as true and zero as false. Use a Bash shebang when relying on Bash-specific arithmetic syntax.
Counter Example
This script initializes an integer variable to zero, prints it, and increments it until it reaches five:
#!/usr/bin/env bash
num=0
until [ "$num" -eq 5 ]; do
printf 'The number is %s.\n' "$num"
((num++))
done
Expected output:
The number is 0.
The number is 1.
The number is 2.
The number is 3.
The number is 4.
The body prints values from 0 through 4. After printing 4, ((num++)) changes the value to 5. Bash then tests [ "$num" -eq 5 ]; the test is true, so the body does not process or print the terminating value.
Use numeric operators such as -eq, -lt, and -ge for numeric intent. String comparisons are not a substitute for numeric comparisons.
Updating Loop State
Every useful loop needs progress. The loop body must change the variable or external state checked by the condition:
((count++))
((count += 1))
count=$((count + 1))
If the condition checks a value that never changes, the condition may remain false forever. This creates an infinite loop. A misplaced continue can cause the same problem if it skips the update.
count=0
until (( count == 3 )); do
printf 'count=%s\n' "$count"
((count += 1))
done
Using Commands as the Condition
The condition does not have to be a comparison. Any command can be used. Its exit status determines whether the loop continues.
Retry a Command Until It Succeeds
This example retries an HTTP health check. The --fail option makes unsuccessful HTTP responses produce a failure status, while --silent and --output /dev/null suppress normal output:
#!/usr/bin/env bash
until curl --fail --silent --output /dev/null https://example.com/health; do
printf '%s\n' 'Service is not ready; retrying...'
sleep 5
done
printf '%s\n' 'Service is ready.'
The delay prevents a rapid retry loop that wastes CPU or floods the service with requests. For remote systems, exponential backoff can be preferable to a fixed delay.
Wait Until a File Appears
A file test can poll for a generated file or completion marker:
#!/usr/bin/env bash
attempt=1
max_attempts=10
until [ -f /tmp/job-complete ]; do
if [ "$attempt" -gt "$max_attempts" ]; then
printf '%s\n' 'Timed out waiting for completion marker.' >&2
exit 1
fi
printf 'Waiting for completion marker (attempt %s of %s)...\n' \
"$attempt" "$max_attempts"
((attempt++))
sleep 2
done
printf '%s\n' 'Completion marker found.'
The retry limit gives the script a bounded termination path. Without it, a missing file could cause the script to wait indefinitely.
Wait for a Network Endpoint with a Limit
A production-oriented retry loop should report failure when its maximum attempts are exhausted:
#!/usr/bin/env bash
attempt=1
max_attempts=6
until curl --fail --silent --output /dev/null http://localhost:8080/health; do
if [ "$attempt" -ge "$max_attempts" ]; then
printf 'Endpoint did not become reachable after %s attempts.\n' \
"$max_attempts" >&2
exit 1
fi
printf 'Endpoint is unavailable; retrying attempt %s.\n' "$((attempt + 1))"
((attempt++))
sleep 3
done
printf '%s\n' 'Endpoint is reachable.'
break and continue
break exits the nearest loop immediately. Use it when an additional event means the loop should end before its normal condition becomes true.
continue skips the rest of the current iteration and starts the next condition evaluation. Update required state before continue, or the loop may never make progress.
count=0
until (( count >= 10 )); do
((count++))
if (( count % 2 == 0 )); then
continue
fi
printf 'Odd value: %s\n' "$count"
if (( count >= 7 )); then
break
fi
done
The increment happens before continue, so even iterations are skipped safely. The break ends the loop once the required odd value has been printed.
Safe Termination and Interrupts
Foreground scripts can usually be interrupted with Ctrl+C. The terminal sends an interrupt signal, commonly handled by Bash by stopping the foreground command or script. This is useful interactively, but unattended scripts cannot depend on a person pressing Ctrl+C.
For external-state loops, use at least one safety mechanism:
- A maximum attempt count.
- A deadline or elapsed-time limit.
- A delay between attempts.
- Exponential backoff for expensive or remote operations.
- Explicit failure handling with a nonzero exit status.
until Versus while
These loops can express inverse forms of the same idea:
# Continues while count is below 5
while [ "$count" -lt 5 ]; do
# commands
done
# Continues until count reaches 5
until [ "$count" -eq 5 ]; do
# commands
done
| Loop type | Body runs when condition is | Stops when condition is | Typical use |
|---|---|---|---|
until | False or nonzero | True or zero | Retrying until success or waiting for a desired state. |
while | True or zero | False or nonzero | Continuing while a state remains valid. |
An until loop is not equivalent to a do-while loop. A do-while loop runs its body once before checking its condition; Bash until checks first and may run zero times.
Use a for loop when iterating over a known list or fixed range. It communicates that the set of items or iterations is already known, whereas until is clearer when stopping depends on a condition becoming true.
Shell Portability
until is available in Bash and POSIX-style shells. The bracket test form, such as [ "$value" -eq 5 ], is portable across those environments when used with valid POSIX operators.
[[ ... ]] provides Bash and some other shell-specific conveniences, but it is not portable POSIX syntax. Likewise, some arithmetic forms and features are Bash-specific. If a script uses Bash-specific syntax, declare that requirement explicitly:
#!/usr/bin/env bash
For maximum shell portability, use a POSIX shell shebang and portable [ ... ] tests. Do not mix Bash-only features into a script intended to run under /bin/sh.
Troubleshooting until Loops
The Loop Never Stops
- Check whether the variable in the condition is updated.
- Make sure an update is not skipped by
continue. - Verify the comparison operator and the value being tested.
- For external commands, confirm that success is possible.
- Add a retry limit and a delay.
printf 'current value: %s\n' "$count"
During debugging, run the condition by itself and inspect its exit status:
[ "$count" -eq 5 ]
echo "$?"
The Conditional Expression Reports an Error
Check for spaces around the bracket command and quote variable expansions:
[ "$value" -eq 5 ]
Use numeric operators for numbers. A string operator used with numeric intent can produce incorrect behavior.
The Loop Runs When It Should Stop
Remember that nonzero means false to the shell. An until loop continues after a nonzero condition status and stops after zero. A condition copied from a while loop may need to be inverted or rewritten.
The Body Never Runs
The condition was already true before the first test. Initialize the state so the condition starts false when at least one iteration is required. If the body must always run once, choose a structure designed for post-test behavior rather than relying on until.
Retries Use Too Much CPU or Network Traffic
Add sleep between attempts, and use a maximum count or deadline. For unreliable remote services, progressively increase the delay with exponential backoff.
Exam-Relevant Summary
untilruns the body while its condition is false.- The loop stops when the condition command returns exit status zero.
- Exit status zero means true or success; nonzero means false or failure.
- The condition is tested before the first iteration, so the body can run zero times.
- Use spaces around
[and], and quote variable expansions. - Use
-eq,-lt, and related operators for numeric comparisons. - Update loop state on every path, including paths involving
continue. - Use
sleep, retry limits, deadlines, and failure exits for external-state loops. breakexits the nearest loop;continuebegins the next iteration.untilis pre-test, so it is not a Bash do-while loop.