Bash while Loop

Tested on RHEL 10.2 (Coughlan)
Package bash 5.2.26-6.el10
Applies to Linux distributions with Bash installed
Privilege Normal user
Scope Bash while and until syntax, exit-status conditions, (( )) counters, while true / while :, command-based retries and polling, a brief while read bridge, the pipeline subshell trap, do-while emulation, and a short break/continue preview. Does not cover full line-by-line file reading, detailed while vs until comparisons, or deep subshell/process-substitution tutorials.
Related guides Bash shell script tutorial
Shell scripting course hub
Short on time?Beta Get the gist of this 11-minute read in seconds, then ask follow-up questions

A Bash while loop repeats its body as long as the command list before do exits with status 0. That condition can be a test such as [[ … ]], an arithmetic expression such as (( … )), or an ordinary command such as read or curl.

When the condition returns non-zero, the loop stops. For fixed word lists, see the Bash for loop instead.

Goal Pattern
Counter loop while (( n < 10 )); do …; done
Test condition while [[ condition ]]; do …; done
Command succeeds while command; do …; done
Command fails while ! command; do …; done
Infinite loop while :; do …; done
Read lines while IFS= read -r line; do …; done < file

Bash while Loop Syntax

A while loop runs COMMANDS in the body, then re-evaluates the condition command list before the next pass:

text
while CONDITION_COMMANDS; do
  BODY_COMMANDS
done

do opens the body and done closes it. Semicolons let you write the header on one line when the body is short:

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

Multiline form is easier to read in scripts. The keyword order is always while …; do … done — Bash has no do … while keyword.


The Condition Is a Command Exit Status

The words between while and do are ordinary shell commands. Bash runs that list and uses the exit status of the last command in the list:

Exit status Loop behavior
0 Run the body (or another iteration after the body)
Non-zero Stop — do not run the body again

You do not need a separate $? check on the condition line. Writing while cmd; do already tests cmd directly — see Bash exit codes for how status values propagate.

A [[ … ]] test is just one kind of command — Bash [ ] vs [[ ]] vs (( )) explains when double brackets are required:

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

Each time through, [[ $a -le 3 ]] exits 0 while a is 1, 2, or 3. After a becomes 4, the test fails and the loop stops.

A while condition can be a command list, so shell operators such as && and || can combine multiple tests. The loop continues only while the final condition list exits with status 0:

bash
n=1

while (( n <= 5 )) && [[ $n -ne 3 ]]; do
  echo "n=$n"
  ((++n))
done
output
n=1
n=2

The loop stops when n reaches 3 because [[ $n -ne 3 ]] fails.


Counter Loops with (( ))

Arithmetic conditions belong in (( … )). The expression returns exit status 0 when it evaluates to non-zero, which fits counter loops cleanly — see counter in Bash for while, until, and for (( )) recipes, and Bash arithmetic for more operators.

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

Update the counter inside the body. If the condition never changes, the loop never ends.


Infinite Loops: while true and while :

Use an always-successful condition when you plan to stop from inside the body with break, return (inside a function), or by exiting the shell.

true and : are shell builtins that always succeed. true is an explicit success command; : is the null command. Either form is idiomatic:

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

The same shape works with while :; do — pick whichever reads clearer in your script. A tight loop with no sleep and no I/O wait can burn CPU; add sleep or block on a real event when you poll.


Run While a Command Succeeds or Fails

Because the condition is a command, prefix it with ! when you want the loop to run while a command keeps failing. That pattern fits retries and “wait until ready” scripts:

text
while ! COMMAND; do
  ...
done

The loop runs until COMMAND exits 0. Below, curl is the command — retry an HTTP health check until it succeeds, with a maximum number of attempts:

bash
attempt=0
max=3

while ! curl -sf --max-time 1 http://127.0.0.1:9/ >/dev/null; do
  attempt=$((attempt + 1))
  echo "attempt=$attempt failed"
  ((attempt >= max)) && break
  sleep 1
done

echo "stopped after attempt=$attempt"
output
attempt=1 failed
attempt=2 failed
attempt=3 failed
stopped after attempt=3

This example uses an endpoint expected to fail on the test system so the retry path is visible. In a real script, replace the URL with the service or health check you need to monitor, and tune sleep so the loop does not busy-spin. See the curl command for -f, -s, and timeouts.

Poll until a background process exits:

bash
sleep 0.15 &
pid=$!

while kill -0 "$pid" 2>/dev/null; do
  sleep 0.05
done

echo "background job finished"
output
background job finished

kill -0 checks whether the process exists; it does not send a signal — see kill and pkill when you need to deliver a signal instead of polling. Combine polling with sleep so the loop does not hammer the kernel.

Prompt until the input matches a simple pattern — the =~ test uses Bash regex ERE syntax. Supply answers from a here-document so the example is reproducible:

bash
while true; do
  read -r answer
  [[ $answer =~ ^[0-9]+$ ]] && break
  echo "Enter digits only (got: $answer)"
done <<'EOF'
abc
42
EOF

echo "valid=$answer"
output
Enter digits only (got: abc)
valid=42

The body runs at least once per line of input; invalid lines print a message and the loop reads again.


Read Input with while read

read is a command like any other, so it can be the while condition. That is the usual bridge to line-oriented input:

bash
while IFS= read -r line || [[ -n $line ]]; do
  printf '%s\n' "$line"
done < file.txt

IFS= preserves leading whitespace; -r avoids backslash escapes. The || [[ -n $line ]] handles a final line with no trailing newline. Redirecting with < file keeps variable assignments in the current shell — read a file line by line in Bash covers larger files and edge cases beyond this bridge pattern.

To reproduce the example without an existing file, create a small sample file:

bash
printf 'one\ntwo\n' > sample.txt

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

rm -f sample.txt
output
read: one
read: two

For interactive prompts, timeouts, arrays, and additional read options, see the Bash read command guide.


The Pipeline Subshell Trap

By default, Bash runs each command in a pipeline in a subshell, including a while read loop on the right side. Because assignments happen in that subshell, the parent shell does not see them afterward:

bash
count=0

printf 'a\nb\nc\n' | while IFS= read -r line; do
  count=$((count + 1))
done

echo "count after pipe=$count"
output
count after pipe=0

The loop incremented count in a subshell; the parent shell still sees 0.

Feed the loop from the current shell instead — process substitution avoids the pipe subshell:

bash
count=0

while IFS= read -r line; do
  count=$((count + 1))
done < <(printf 'a\nb\nc\n')

echo "count after process substitution=$count"
output
count after process substitution=3

Input redirection (while read …; done < file) also keeps assignments in the current shell. Bash's lastpipe option can change the behavior of the final pipeline command, but input redirection or process substitution is usually clearer and more portable in scripts.


Bash until as the Inverse of while

until uses the same syntax as while but runs the body while the condition command fails (non-zero exit status). It stops once the condition succeeds:

text
until CONDITION_COMMANDS; do
  BODY_COMMANDS
done

Wait until a flag becomes true — here simulated with a counter:

bash
ready=0
tries=0

until ((ready)); do
  tries=$((tries + 1))
  echo "try=$tries"
  ((tries >= 2)) && ready=1
  sleep 0
done

echo "ready=$ready"
output
try=1
try=2
ready=1

while ! cmd and until cmd are equivalent; pick the form that matches how you describe the problem aloud. For a deeper comparison of both loop forms, see Bash until vs while loop.


Emulate a do ... while Loop in Bash

Bash has no do { … } while (condition); keyword. To run the body at least once before a real test, use while : (or while true) and break when the exit condition is met:

bash
n=0

while :; do
  echo "n=$n"
  ((++n))
  ((n >= 3)) && break
done
output
n=0
n=1
n=2

The first iteration always runs because : always succeeds. The break fires once n reaches 3, before printing that value.


break and continue

break leaves the innermost loop immediately. continue skips the rest of the current body and jumps to the next condition test:

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

Iteration k=2 is silent because of continue. Value 5 triggers break before the echo. Nested loops, break 2 / continue 2, and break vs exit are in Bash break and continue — keep break inside the loop and exit for leaving the whole script.


Common while Loop Problems

Symptom Likely cause Fix
Loop never ends Condition or counter never changes Update the variable the test reads; add break with a max count
High CPU while waiting No sleep in a poll/retry loop Add sleep or block on I/O
Variable unchanged after cmd | while read By default, the loop ran in a pipeline subshell Use < file, here-string, or process substitution
Extra if around the condition Misunderstanding exit status Use while cmd or while ! cmd directly
while true runs past intended stop break missing or wrong scope Put break in the same shell that owns the loop

Summary

A Bash while loop repeats its body while the command list before do exits with status 0. That single rule covers [[ … ]], (( … )), read, curl, kill -0, and any other command you can run in the shell.

Use (( … )) for arithmetic counters, while true or while : when you will stop with break, and ! on the condition when you need to loop while a command keeps failing. Add sleep in retry and poll loops so they do not busy-spin.

For line input, while IFS= read -r line; do … done < file is the minimal pattern; by default, pipe-fed read loops run in a subshell and parent variables stay unchanged. until is the same mechanism with inverted polarity — use it when “keep going until success” reads more clearly than negating a while test. Bash has no do-while keyword; while :; do …; condition && break; done runs the body at least once.

When the input is a fixed list of words rather than a repeating condition, switch to a for loop instead.


References


Frequently Asked Questions

Does Bash have a do-while loop?

Bash does not have a dedicated do … while construct. To run the body at least once before checking the exit condition, use while :; do …; condition && break; done so the first iteration always runs and break stops the loop when your real test succeeds.

What is the difference between while true and while :?

Both are Bash builtins that return success. true is an explicit success command; : is the null command. Either works for an infinite loop, so use whichever reads more clearly.

Why is my variable lost after command | while read?

By default in Bash, a pipe-fed while read loop runs in a subshell, so assignments made inside it are not visible afterward. Redirect input with < file, a here-string, or process substitution so read runs in the current shell.

When should I use until instead of while?

Use until when the loop should keep running while a command fails and stop on the first success — for example waiting until a service responds. while ! cmd is equivalent; pick whichever reads more naturally. Detailed comparison tables live in the dedicated until-versus-while guide.

Can while test a command directly?

Yes. The condition is always a command list. Write while curl -sf …; do or while ! systemctl is-active …; do — do not wrap the command in if and then test $? on a separate line unless you need the output for something else.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)