You create an executable script that continually retries a failed network or database connection, but it only runs once and exits. In other cases, the condition logic is reversed, causing the loop to continue indefinitely. If either situation sounds familiar, you are in the right place.
I once wrote a backup script for a production environment that used a while loop to wait for a mount point. It looked perfect on paper and worked flawlessly in my lab environment. But over the weekend, it ran endlessly because a condition I assumed would eventually change never did. That experience taught me more about loop logic than any tutorial ever could. This guide is designed for everyone, from beginners writing their first loop to system administrators trying to avoid second-guessing whether to use while or until. By the end of this article, you will understand exactly when to use each one, along with practical scripts you can directly integrate into your workflows.
Real Scenario:
Think about any task you do repeatedly on a Linux server: checking if a service is up, waiting for a file to appear, retrying an API call, or rotating old logs. All of those have one thing in common. They need a loop that runs based on a condition, not a fixed count. That is exactly what while and until solve. Once you understand both, a whole category of automation scripts just clicks into place.
If you are new to bash scripting and want to understand the foundation first, check out this guide on what bash scripting is and how it works.
bash while and until Loops: The Logic Difference That Trips People Up
Most tutorials just show you syntax and move on. But the confusion between while and until is almost always a logic problem, not a syntax problem. So let's clear it up first.
A while loop runs as long as the condition is true. The moment it becomes false, the loop stops. An until loop does the opposite. It runs as long as the condition is false, and stops when it becomes true. Same outcome, opposite logic.
Note:
Bash checks the condition before each iteration for both loops. If the condition is already satisfied on the first check, the loop body never runs at all. This catches people off guard when a variable starts at an unexpected value.
Here is the most direct way to see the difference side by side. Both loops print numbers 1 through 5, but the condition logic is reversed.
LinuxTeck.com
# while loop: runs while condition is TRUE
count=1
while [ $count -le 5 ]; do
echo "while: $count"
((count++))
done
# until loop: runs while condition is FALSE
count=1
until [ $count -gt 5 ]; do
echo "until: $count"
((count++))
done
while: 2
while: 3
while: 4
while: 5
until: 1
until: 2
until: 3
until: 4
until: 5
Same result. The choice between them comes down to which reads more naturally for your specific condition. If you are checking "keep going while X is running," use while. If you are thinking "keep going until X succeeds," use until.
The while Loop: Syntax and How It Actually Works
The basic structure is simple. The condition goes between square brackets, and everything between do and done is the loop body.
LinuxTeck.com
# Basic while loop reading a file line by line
while IFS= read -r line; do
echo "Line: $line"
done < /etc/hostname
# while loop with a counter
attempt=1
max=5
while [ $attempt -le $max ]; do
echo "Attempt $attempt of $max"
((attempt++))
done
Attempt 1 of 5
Attempt 2 of 5
Attempt 3 of 5
Attempt 4 of 5
Attempt 5 of 5
The IFS= read -r line pattern is worth understanding. IFS= clears the field separator so leading spaces are preserved. The -r flag stops backslash sequences from being interpreted. Without both, filenames or config lines with special characters break in ways that are hard to debug.
The until Loop: When It Reads Better Than while
until is less common but genuinely useful when your natural thought process is "keep trying until this works." The syntax mirrors while exactly. Only the condition logic flips.
LinuxTeck.com
# until loop: keep retrying until ping succeeds
attempt=0
until ping -c 1 8.8.8.8 > /dev/null 2>&1; do
attempt=$((attempt + 1))
echo "Network not reachable, attempt $attempt"
sleep 3
done
echo "Network is up after $attempt attempts"
Network not reachable, attempt 2
Network is up after 2 attempts
Try reading that out loud: "until ping succeeds, keep waiting." That reads naturally. Rewriting it as a while loop would require negating the condition with ! which is slightly less readable. Use whichever one your condition fits more naturally.
Real Script Examples: From Simple to Production-Ready
Syntax alone does not teach you when to actually use loops. These examples cover the full range from a first script to real automation patterns.
Example 1: Monitor disk usage and alert when it crosses a threshold
LinuxTeck.com
# Disk usage monitor — runs indefinitely
THRESHOLD=85
while true; do
USAGE=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$USAGE" -gt "$THRESHOLD" ]; then
echo "[ALERT] Disk at ${USAGE}% on $(hostname)"
else
echo "[OK] Disk at ${USAGE}%"
fi
sleep 60
done
[OK] Disk at 43%
[ALERT] Disk at 87% on webserver01
Example 2: Wait for a service to become available before continuing
This pattern is common in deployment scripts and container startup sequences. You need to wait for a database or API before running migrations.
LinuxTeck.com
# Wait for a web service to respond before continuing
MAX_WAIT=30
attempt=0
until curl -sf http://localhost:8080/health > /dev/null; do
attempt=$((attempt + 1))
if [ $attempt -ge $MAX_WAIT ]; then
echo "Service did not respond after $MAX_WAIT attempts. Exiting."
exit 1
fi
echo "Waiting... attempt $attempt of $MAX_WAIT"
sleep 2
done
echo "Service is ready. Proceeding."
Waiting... attempt 2 of 30
Service is ready. Proceeding.
Example 3: Read a config file and process each line
LinuxTeck.com
# Process each server from a list file
# servers.txt contains one hostname per line
while IFS= read -r server || [ -n "$server" ]; do
# Skip empty lines, blank lines, and comments (including indented ones)
[[ -z "$server" || "$server" =~ ^[[:space:]]*(#|$) ]] && continue
echo "Checking $server..."
ping -c 1 -W 1 "$server" > /dev/null 2>&1 && echo " UP" || echo " DOWN"
done < /etc/servers.txt
UP
Checking web02...
UP
Checking db01...
DOWN
Example 4: Auto-rotate logs older than N days (real-world automation)
This is the kind of script that runs in a weekly cron job on most production servers. You can pair this with cron job scheduling to automate the cleanup completely.
LinuxTeck.com
# Rotate logs older than 7 days in /var/log/myapp
LOG_DIR="/var/log/myapp"
MAX_DAYS=7
DELETED=0
while IFS= read -r -d "" logfile; do
AGE=$(( ( $(date +%s) - $(stat -c %Y "$logfile") ) / 86400 ))
if [ "$AGE" -gt "$MAX_DAYS" ]; then
rm "$logfile"
echo "Removed: $logfile (${AGE} days old)"
((DELETED++))
fi
done < <(find "$LOG_DIR" -name "*.log" -print0)
echo "Done. Removed $DELETED log files older than $MAX_DAYS days."
Removed: /var/log/myapp/app-2026-05-14.log (13 days old)
Done. Removed 2 log files older than 7 days.
break and continue: Controlling What Happens Inside the Loop
Two keywords give you fine-grained control over loop flow. break exits the loop entirely. continue skips the rest of the current iteration and jumps to the next one.
Most tutorials show trivial examples. Here is one that actually matters in practice: processing a list and stopping as soon as a condition is met, rather than wastefully looping through the rest.
LinuxTeck.com
# Scan log for first critical error and stop
# continue skips info/warn lines, break exits on CRITICAL
while IFS= read -r line; do
# Skip non-error lines
[[ "$line" != *ERROR* && "$line" != *CRITICAL* ]] && continue
echo "Found: $line"
# Stop at the first CRITICAL
if [[ "$line" == *CRITICAL* ]]; then
echo "Stopping at first CRITICAL event."
break
fi
done < /var/log/myapp/app.log
Found: [2026-05-27 02:19:41] CRITICAL - Database connection lost
Stopping at first CRITICAL event.
You can also use break 2 to exit two levels of nested loops at once. Useful but easy to overuse. If you find yourself needing break 3, that is usually a sign to refactor the script.
The Mistake That Causes Infinite Loops (and How to Debug It)
Every bash scripter has written an infinite loop they did not mean to write. Usually it is one of two things: forgetting to increment a counter, or getting the condition direction wrong for until.
Common Mistake:
Forgetting to update the loop variable inside the body. The loop runs forever because the condition never changes. This is especially common in while loops with counters.
Wrong:
count=1; while [ $count -le 10 ]; do echo $count; done
The variable count never changes, so the condition stays true and the loop never stops. Always make sure whatever variable drives your condition gets updated inside the loop body. Fix: add ((count++)) as the last line before done. If you do get stuck in an infinite loop, press Ctrl+C to interrupt it.
The second issue is condition direction in until. People sometimes use the same condition they would use in a while loop, which causes the loop to never run at all because the condition is already true on the first check.
For debugging loops, use bash -x to trace execution. It prints each command with its expanded values before it runs, which makes it easy to spot where a condition is wrong or a variable is not updating.
LinuxTeck.com
bash -x ./your-script.sh
# Or add set -x inside the script to trace from that point
#!/bin/bash
set -x # enable tracing
count=1
while [ $count -le 3 ]; do
echo "count is $count"
((count++))
done
set +x # disable tracing
+ '[' 1 -le 3 ']'
+ echo 'count is 1'
count is 1
+ (( count++ ))
+ '[' 2 -le 3 ']'
+ echo 'count is 2'
count is 2
+ (( count++ ))
+ '[' 3 -le 3 ']'
+ echo 'count is 3'
count is 3
+ (( count++ ))
+ '[' 4 -le 3 ']'
+ set +x
Each line prefixed with + shows exactly what Bash is evaluating at each step. You can see the variable expanding and the condition being checked. It is the fastest way to figure out why a loop is behaving unexpectedly.
For more on making your scripts robust from the start, take a look at bash exit codes and error handling to understand how loop exit status and error trapping work together.
What These Loops Actually Unlock in Your Automation Workflow
Once you are comfortable with while and until, you stop thinking of scripts as one-shot commands and start thinking in terms of conditions and states. That shift changes what you can automate.
Health checks that retry until a service comes up. Deployment scripts that wait for a container to be ready before running migrations. Log monitors that flag the first anomaly and stop. Backup scripts that cycle through a list of directories and skip ones that do not exist. All of these follow the same pattern: loop, check a condition, act, repeat or stop.
The for loop is great when you know the list upfront. But a lot of real sysadmin work involves conditions you cannot predict ahead of time. A service might start in two seconds or twenty. A file might appear in ten seconds or not at all. That is exactly what condition-based loops were designed for, and once you have the pattern in your head, writing those scripts becomes second nature.
The official GNU Bash manual section on looping constructs is worth bookmarking as a reference. It covers edge cases and POSIX compatibility notes you will eventually hit in production.
FAQ
Why does my while loop run forever even though I set the condition correctly?
Almost always because the variable driving the condition is not being updated inside the loop. Check that you have an increment or assignment inside the loop body. Also check that the variable was initialized before the loop. If $count is empty, the comparison [ $count -le 5 ] can behave unexpectedly. Use bash -x yourscript.sh to trace each step and see what is actually being evaluated.
When should I use until instead of while with a negated condition?
Use whichever reads more naturally for the specific logic. If you are thinking "keep going until the service is up," until reads cleanly. If you would have to say "while the service is NOT up," that is two mental steps. Generally until works best for "wait for success" patterns and retry logic. while works best for "keep running while this is true" patterns like monitoring loops and counter-based iteration.
Why does my script loop correctly in the terminal but fail when I run it via cron?
Cron runs with a stripped-down environment. PATH is minimal, so commands that work interactively may not be found by the script. Use full paths for all commands inside loops: /usr/bin/curl instead of just curl. Also check that your shebang line is present and correct. A loop that waits on a network or file condition can also hang indefinitely in cron if no timeout is set. Always add a max attempt counter to loops that depend on external conditions.
What is IFS= read -r and do I always need it?
You need it whenever you are reading lines from a file that might contain leading or trailing whitespace, backslashes, or filenames with spaces. IFS= clears the field separator so leading whitespace is preserved. -r prevents backslash sequences from being interpreted. For simple cases where you know the data is clean, you can skip it. For anything reading config files, paths, or user-generated content, always use it.
How do I safely add a timeout to a loop that's waiting for something?
Add a counter variable and check it on each iteration. Set a max value before the loop. Inside the loop body, increment the counter and exit with a non-zero status if it hits the limit. This pattern works for both while and until loops. You can also use the timeout command to wrap external commands inside the loop, so individual commands do not block indefinitely.
Is there a way to run loop iterations in parallel instead of one at a time?
Yes. Append & to the command inside the loop to run it in the background. Capture each background job's PID with $! and then wait for all of them with a second for loop over the collected PIDs using wait $pid. This is practical for server health checks or batch operations where each iteration is independent. For complex parallel workflows, look into xargs -P or GNU parallel, which handle process limits and output more cleanly.
Summary
Now that you have bash while and until loops working in your scripts, the next practical step is combining them with proper error handling so they fail gracefully instead of silently. The pattern of looping until a condition is met, with a timeout escape, is one of the most reusable pieces of bash scripting you will write. Start with the disk monitor or service wait examples and adapt them to something in your own environment. You will learn more from that than from reading five more tutorials.
Related Articles
- Bash for Loop in Linux With Practical Examples (Part 18 / 34)
- Bash if elif else Statement With Examples (Part 10 of 34)
- Bash Script Exit Codes and Error Handling (Part 5 of 34)
- Bash case Statement With Examples (Part 12 / 34)
- Linux Bash Scripting Automation Guide 2026
Learn step-by-step how to automate Linux tasks with real-world scripts and practical examples.