Shell Loop Constructs: for, while, until, and select
Shell scripts provide several loop mechanisms for repetitive task automation. This guide covers the main loop types available in Bash.
Loop Types Overview
| Loop Type | Purpose |
|---|---|
| for | Iterate over a list or sequence |
| while | Execute while a condition is true |
| until | Execute until a condition becomes true |
| select | Create numbered menus |
For Loop
The for loop in Shell operates in three variations.
List-Based Iteration
for variable in item1 item2 item3
do
commands
done
The loop assigns each list item to the variable sequential, executing the loop body for each value until the list is exhausted. List items are separated by whitespace.
Example - Iterating through host addresses:
#!/bin/bash
for host in 192.168.72.130 192.168.72.131 192.168.72.132
do
echo "Pinging $host"
ping -c 1 -W 1 "$host" > /dev/null 2>&1 && echo "$host is reachable"
done
Using brace expansion:
#!/bin/bash
for num in {0..3}
do
echo "Server-$num"
done
Generating sequences with seq:
#!/bin/bash
for item in $(seq 1 2 10)
do
echo "Processing item $item"
done
Specifying step increments:
#!/bin/bash
for n in {1..10..2}
do
echo "Odd number: $n"
done
Filtering directory contents:
#!/bin/bash
for entry in $(ls /etc)
do
if [ -f "/etc/$entry" ]; then
echo "File: $entry"
fi
done
Processing words with length check:
#!/bin/bash
words="hello world rabbit favorite eat apple cabbage"
for w in $words
do
length=${#w}
if [ $length -le 6 ]; then
echo "$w (length: $length)"
fi
done
Loop Without Explicit List
for variable
do
commands
done
This form iterates over positional parameters.
#!/bin/bash
for arg in "$@"
do
echo "Argument: $arg"
done
C-Style For Loop
for ((expr1; expr2; expr3))
do
commands
done
Example - Creating user accounts:
#!/bin/bash
for ((counter=1; counter<=25; counter++))
do
if [ $counter -lt 10 ]; then
username="test0$counter"
else
username="test$counter"
fi
if ! id "$username" &>/dev/null; then
useradd "$username"
echo "DefaultPass1!" | passwd --stdin "$username" &>/dev/null
echo "Created user: $username"
fi
done
Alternative approach using seq formatting:
#!/bin/bash
for u in $(seq -f "user%02g" 1 20)
do
if ! id "$u" &>/dev/null; then
useradd "$u"
echo "Created: $u"
fi
done
While Loop
Basic Syntax
while [ condition ]
do
commands
done
Example - Counting with while:
#!/bin/bash
count=1
while [ $count -le 10 ]
do
echo "Count: $count"
((count++))
done
Reading files line by line - Method 1:
#!/bin/bash
filename="data.txt"
while IFS= read -r line
do
echo "$line"
done < "$filename"
Reading files line by line - Method 2:
#!/bin/bash
exec < "data.txt"
while read -r content
do
echo "$content"
done
Infinite Loops
Three approaches for continuous execution:
while true; do
commands
done
while :; do
commands
done
while [ 1 -eq 1 ]; do
commands
done
Practical Examples
Number guessing game:
#!/bin/bash
target=$((RANDOM % 50 + 1))
attempts=0
max_attempts=5
while true
do
read -p "Enter a number (1-50): " guess
((attempts++))
if [ "$guess" -eq "$target" ]; then
echo "Correct! You got it in $attempts tries."
exit 0
elif [ "$guess" -gt "$target" ]; then
echo "Too high, try again."
else
echo "Too low, try again."
fi
if [ $attempts -ge $max_attempts ]; then
echo "Game over! The number was $target."
exit 1
fi
done
Parsing structured data:
Input file servers.txt:
192.168.1.10 8080
192.168.1.11 9090
192.168.1.12 80
#!/bin/bash
while read -r ip port
do
echo "Server IP: $ip, Service Port: $port"
done < servers.txt
Until Loop
The until loop contiunes execution while the condition is false, stopping when it becomes true.
until [ condition ]
do
commands
done
Example - Counting to five:
#!/bin/bash
i=0
until [ $i -ge 5 ]
do
echo "Value: $i"
i=$((i + 1))
done
Comparing while and until:
#!/bin/bash
number=0
# Using while - loop runs while condition is true
while [ $number -lt 3 ]
do
echo "While: $number"
((number++))
done
# Using until - loop runs until condition becomes true
number=0
until [ $number -ge 3 ]
do
echo "Until: $number"
((number++))
done
Select Loop
The select construct generates a numbered menu from a list.
select variable [ in list ]
do
commands
done
Example - Database version selection:
#!/bin/bash
echo "Select database version:"
select db_ver in "MariaDB-10.3" "MariaDB-10.5" "MariaDB-10.6" "PostgreSQL-14"
do
echo "Installing $db_ver..."
break
done
Example - Application menu:
#!/bin/bash
echo "Choose an application:"
select app in "Web Server" "Database" "Cache" "Message Queue" "Exit"
do
case $app in
"Exit") echo "Goodbye"; break ;;
*) echo "Selected: $app" ;;
esac
done
Example - Color selection with auto-exit:
#!/bin/bash
select color in "Red" "Green" "Blue" "Yellow" "Quit"
do
case $REPLY in
5) echo "Exiting..."; break ;;
*) echo "You selected: $color" ;;
esac
done
Nested Loops
Loops can be nested within other loops for complex iterations.
Example - Multiplication table:
#!/bin/bash
for row in {1..9}
do
line=""
for col in {1..9}
do
if [ $col -le $row ]; then
product=$((row * col))
line+="${row}x${col}=${product} "
fi
done
echo "$line"
done
Example - Diamond pattern:
#!/bin/bash
rows=7
for ((r=1; r<=rows; r++))
do
for ((s=rows-r; s>0; s--))
do
echo -n " "
done
for ((c=1; c<=r; c++))
do
echo -n "* "
done
echo
done
for ((r=rows-1; r>=1; r--))
do
for ((s=rows-r; s>0; s--))
do
echo -n " "
done
for ((c=1; c<=r; c++))
do
echo -n "* "
done
echo
done
Example - File processing within directories:
#!/bin/bash
for dir in /tmp/project1 /tmp/project2 /tmp/project3
do
if [ -d "$dir" ]; then
echo "Processing directory: $dir"
count=0
for file in "$dir"/*
do
if [ -f "$file" ]; then
((count++))
fi
done
echo " Found $count files"
fi
done
Loop Control: break and continue
break Statement
The break statement immediately exits the innermost loop.
#!/bin/bash
for n in {1..10}
do
if [ $n -eq 7 ]; then
echo "Found target at $n, stopping"
break
fi
echo "Checking $n"
done
echo "Loop terminated"
Breaking outer loops with label:
#!/bin/bash
outer_loop:
for i in {1..5}
do
for j in {1..5}
do
if [ $((i * j)) -eq 12 ]; then
echo "Found match: $i x $j = 12"
break 2
fi
done
done
continue Statement
The cotninue statement skips the rest of the current iteration and moves to the next cycle.
#!/bin/bash
for n in {1..10}
do
if [ $((n % 2)) -eq 0 ]; then
continue
fi
echo "Odd number: $n"
done
Skipping specific values:
#!/bin/bash
for status in active inactive suspended pending deleted
do
if [ "$status" = "deleted" ]; then
continue
fi
echo "Processing $status accounts"
done
Using continue in nested loops:
#!/bin/bash
for outer in {1..3}
do
for inner in {1..3}
do
if [ $inner -eq 2 ]; then
continue
fi
echo "$outer - $inner"
done
done