Bash Loops: for, while and until
Learn all three Bash loop types — for, while, and until — with practical, copy-paste examples covering file iteration, counting, polling, and safe line reading.
Before you start
- ▸A terminal with Bash 4.0 or later (check with: bash --version)
- ▸Basic familiarity with running shell commands
- ▸A text editor for writing script files (nano, vim, or any editor)
Loops are the backbone of any non-trivial shell script. Bash gives you three distinct loop constructs — for, while, and until — each suited to different situations. for iterates over a fixed list or range; while runs as long as a condition is true; until runs as long as a condition is false. Master all three and you can automate almost any repetitive system task.
The for Loop
The for loop is the right choice when you already know the set of items to iterate over — a list of files, a range of numbers, or command output.
Iterating over a word list
The simplest form loops over a literal list of words:
for distro in debian ubuntu fedora arch; do
echo "Processing: $distro"
done
Iterating over a numeric range with brace expansion
Use brace expansion for a compact numeric range. The third value is the step size.
# Count 1 to 10
for i in {1..10}; do
echo "Step $i"
done
# Even numbers only (step of 2)
for i in {0..20..2}; do
echo "$i"
done
C-style for loop
When you need a counter with more control, the C-style syntax is cleaner:
for (( i=0; i<5; i++ )); do
echo "Index: $i"
done
Looping over files
Globbing inside a for loop is one of the most practical patterns in shell scripting. Always quote the variable when using the result.
# Rename every .txt file to .bak in the current directory
for file in *.txt; do
mv -- "$file" "${file%.txt}.bak"
echo "Renamed: $file"
done
Looping over command output
Use command substitution to feed output into a for loop. Be aware this splits on whitespace; for filenames with spaces, prefer a while read loop (shown later).
for user in $(cut -d: -f1 /etc/passwd); do
echo "User found: $user"
done
The while Loop
while keeps executing its body as long as the test command returns exit code 0 (success). It is ideal when the number of iterations is not known in advance.
Basic while with a counter
count=1
while [ $count -le 5 ]; do
echo "Count is $count"
(( count++ ))
done
Reading a file line by line
This is the correct and safe way to process files that may contain spaces or special characters. It handles blank lines correctly and does not split on internal whitespace.
while IFS= read -r line; do
echo "Line: $line"
done < /etc/hosts
IFS= prevents leading/trailing whitespace from being stripped. -r prevents backslash interpretation. These two options together are the standard safe idiom.
Reading command output safely
Pipe command output into while read to safely handle filenames or any data with spaces:
find /var/log -name "*.log" -print0 | while IFS= read -r -d '' logfile; do
echo "Found log: $logfile"
done
-print0 and -d '' use null bytes as delimiters, making this safe for filenames containing spaces or newlines.
Waiting for a condition — polling pattern
A common sysadmin pattern: wait until a service becomes reachable before proceeding.
echo "Waiting for sshd on 192.168.1.50..."
while ! nc -z 192.168.1.50 22 2>/dev/null; do
echo "Not ready yet, retrying in 5 seconds"
sleep 5
done
echo "SSH is up"
Infinite loop with a break condition
Use while true for a deliberate infinite loop, then break out with break:
while true; do
read -rp "Enter 'quit' to exit: " input
if [[ $input == "quit" ]]; then
echo "Exiting."
break
fi
echo "You typed: $input"
done
The until Loop
until is the logical inverse of while: it loops as long as the condition is false (non-zero exit code). Use it when the natural phrasing of your logic is "keep going until something is ready." In practice many scripts use while ! instead, but until can read more clearly.
Basic until with a counter
count=1
until [ $count -gt 5 ]; do
echo "Count is $count"
(( count++ ))
done
Waiting for a file to appear
A classic use case: block a script until another process creates a lock or result file.
until [ -f /tmp/job_complete ]; do
echo "Job not finished yet, sleeping..."
sleep 3
done
echo "Job complete file detected, continuing."
Retry a command until it succeeds
attempts=0
until ping -c1 -W2 8.8.8.8 &>/dev/null; do
(( attempts++ ))
echo "Attempt $attempts: network unreachable, waiting 10s"
sleep 10
done
echo "Network is up after $attempts retries"
Loop Control: break and continue
Both break and continue work in all three loop types. They accept an optional numeric argument to target an outer loop in nested situations.
# continue skips even numbers; break stops at 8
for i in {1..10}; do
if (( i % 2 == 0 )); then
continue
fi
if (( i == 9 )); then
break
fi
echo "$i"
done
# Output: 1 3 5 7
Practical Example: Batch User Account Creation
This brings several concepts together: reading a file with while read, checking conditions, and acting on each record.
#!/usr/bin/env bash
# users.txt contains one username per line
while IFS= read -r username; do
if id "$username" &>/dev/null; then
echo "Skipping $username — already exists"
continue
fi
useradd -m -s /bin/bash "$username"
echo "Created user: $username"
done < users.txt
Verification
Test any loop safely before running it on real data by replacing destructive commands with echo (a dry-run). For scripts, run them through bash -n to check syntax without execution, and bash -x to trace execution line by line:
# Syntax check only
bash -n myscript.sh
# Trace execution
bash -x myscript.sh
Troubleshooting
- Infinite loop accidentally created: Press Ctrl+C to interrupt. Add a counter and a
breakas a safety valve in loops that wait on external conditions. - Loop over files misses names with spaces: Never use
for file in $(ls). Use a glob (for file in *.txt) orwhile IFS= read -r -d '' file; do ... done < <(find ... -print0). - Variable not incrementing: Ensure you are using
(( count++ ))orcount=$(( count + 1 )). Usingcount++without the double parentheses does nothing in Bash. - Loop body runs in a subshell: When piping into a loop (
cmd | while read), variable changes inside the loop are not visible outside on Bash versions before 4.2. Use process substitution instead:while read line; do ...; done < <(cmd).
Frequently asked questions
- What is the difference between while and until in Bash?
- while loops as long as its condition returns exit code 0 (true/success). until loops as long as its condition returns a non-zero exit code (false/failure). They are logical inverses; anything you can write with one you can write with the other by negating the condition.
- Why should I avoid 'for file in $(ls)'?
- Command substitution splits output on whitespace, so filenames containing spaces or newlines are broken into multiple tokens. Use a glob pattern like 'for file in *' instead, which Bash expands safely without word-splitting.
- Why do variable changes inside a piped while loop disappear after the loop?
- When you write 'cmd | while read', Bash runs the while loop body in a subshell, so variable assignments are lost when the subshell exits. Use process substitution instead: 'while read line; do ...; done < <(cmd)' to keep the loop in the current shell.
- How do I loop over an array in Bash?
- Use the '@' expansion: 'for item in "${myarray[@]}"; do ... done'. The double quotes around '${myarray[@]}' ensure each element is treated as a single word even if it contains spaces.
- How do I add a timeout to a while or until loop that polls for a condition?
- Track elapsed time with a counter or compare against a deadline using the SECONDS built-in variable. For example: 'while ! condition && (( SECONDS < 60 )); do sleep 2; done' will give up after 60 seconds.
Related guides
Bash Arrays and Associative Arrays
Master bash indexed and associative arrays: declaration, element access, looping, mapfile, namerefs, and practical patterns for real scripting work.
Bash Functions and Variable Scoping
Master Bash function scoping with local variables, source-based libraries, correct use of return codes, and array passing techniques including namerefs.
Bash Scripting for Beginners
Learn Bash scripting from scratch: shebang lines, variables, conditionals, loops, and arguments, plus a real backup script to tie it all together.
Build a Real Command-Line Tool in Shell
Build a proper CLI tool in Bash with strict mode, long/short argument parsing, --help, usage(), and clean packaging via install and a Makefile.