while / until
| Since: | POSIX(sh互換) |
|---|
The while statement in shell scripts repeats a block of code as long as the specified condition is true (exit status 0). The basic form is while condition; do ... done. Using until instead loops while the condition is false (non-zero exit status). The pattern of creating an infinite loop with while true and exiting with break is widely used for interactive menus and daemon-style polling. The while read idiom is the standard way to read a file or standard input line by line.
Basic Syntax
Both while and until open their block with do and close it with done. The condition can be a [ expression ], a [[ expression ]], a command, or the built-ins true / false.
| Keyword | Meaning | Description |
|---|---|---|
while | Loop while condition is true | Repeats the do...done block as long as the condition command exits with status 0 (true). |
until | Loop while condition is false | Loops as long as the condition command exits with a non-zero status (false). It is the logical inverse of while. |
do | Start of the body block | Marks the beginning of the commands to repeat. |
done | End of the body block | Closes the loop. |
break | Exit the loop | Exits the current loop immediately. Used to specify the termination condition of an infinite loop. |
continue | Skip to the next iteration | Skips the remaining commands in the body and proceeds to the next condition evaluation. |
# Basic while form
while condition; do
commands
done
# Basic until form (inverse condition of while)
until condition; do
commands
done
# Infinite loop (while true)
while true; do
commands
if exit_condition; then
break
fi
done
# Standard idiom to read a file line by line
while IFS= read -r line; do
echo "${line}"
done < filename
while Loop
You can use a counter variable for counted repetition, or use the exit status of an external command to wait for a condition. To increment a counter, use ((counter++)) in bash or counter=$((counter + 1)) for POSIX sh compatibility.
| Form | Description |
|---|---|
while [ "$i" -lt 5 ] | Loops while variable i is less than 5. |
while [[ "$str" != "quit" ]] | Loops while variable str is not equal to quit. |
while command | Loops while the command exits with status 0. Useful for waiting until a ping or curl succeeds. |
while true | Always true, so this creates an infinite loop. Use break to exit explicitly. |
until Loop
until loops while its condition is false, which lets you express "wait until something happens" directly. Writing while ! condition produces the same behavior, but until makes the intent clearer.
| Form | Equivalent while form | Description |
|---|---|---|
until [ "$i" -ge 5 ] | while [ "$i" -lt 5 ] | Loops until variable i reaches 5 or more. |
until [ -f "$file" ] | while [ ! -f "$file" ] | Waits until the file exists. |
until ping -c1 host | while ! ping -c1 host | Retries until the host is reachable. |
Infinite Loop (while true)
Because while true always evaluates to true, it repeats indefinitely. The standard approach is to use break inside the loop to specify the exit condition explicitly. This pattern is commonly used for interactive menus, polling loops, and long-running daemon processes.
| Pattern | Description |
|---|---|
| Interactive menu | Displays a menu repeatedly until the user selects the exit option. |
| Retry loop | Repeats at a fixed interval until an operation succeeds. Combine with sleep. |
| Polling | Periodically checks the state of a file or process. |
Combining with read (Line-by-Line Input)
The standard idiom for processing a file or standard input line by line is while IFS= read -r line; do ... done < file. Setting IFS= preserves leading and trailing spaces and tabs, and -r disables backslash escape processing.
| Option / Syntax | Description |
|---|---|
IFS= | Prevents trimming of leading and trailing whitespace (spaces and tabs). Without it, leading and trailing whitespace is stripped. |
-r | Treats backslashes as literal characters. Without it, backslashes may be interpreted as line-continuation escapes. |
done < file | Passes the file contents as standard input to the loop. |
command | while read | Pipes a command's output into the loop. Note that a pipeline runs in a subshell, so variables modified inside the loop are not inherited by the parent shell. |
while read -r col1 col2 | Splits each line into multiple variables. The delimiter is determined by the value of IFS (default: space and tab). |
Sample Code
steinsgate_while_counter.sh
#!/bin/bash
# -----------------------------------------------
# Demonstrates while loop (counter-controlled)
# using Steins;Gate characters
# -----------------------------------------------
# -----------------------------------------------
# Store lab member codenames in an array
# -----------------------------------------------
members=("Hououin Kyouma" "Assistant" "Mayuri" "Daru" "Kiryuu Moeka")
codes=(001 002 003 004 005)
echo "=== Future Gadget Laboratory - Lab Member Registration ==="
echo ""
# Loop from counter i = 0 while i is less than the array length
i=0
while [ "$i" -lt "${#members[@]}" ]; do
# Use printf to align the number and codename
printf " Lab Member No.%03d %s\n" "${codes[$i]}" "${members[$i]}"
# Increment the counter (compatible with POSIX sh)
i=$((i + 1))
done
echo ""
echo "Registration complete. Total lab members: ${#members[@]}"
$ chmod +x steinsgate_while_counter.sh $ ./steinsgate_while_counter.sh === Future Gadget Laboratory - Lab Member Registration === Lab Member No.001 Hououin Kyouma Lab Member No.002 Assistant Lab Member No.003 Mayuri Lab Member No.004 Daru Lab Member No.005 Kiryuu Moeka Registration complete. Total lab members: 5
steinsgate_until.sh
#!/bin/bash
# -----------------------------------------------
# Demonstrates until loop
# using Steins;Gate characters
# -----------------------------------------------
# -----------------------------------------------
# Count up a time-leap counter using until
# -----------------------------------------------
# Counter for Hououin Kyouma's (Okabe Rintaro's) time leaps
leap_count=0
# Keep leaping until this many time leaps have been performed
target_leap=5
echo "=== Time Leap Sequence Start ==="
echo "Goal: observe world line divergence after ${target_leap} time leaps"
echo ""
# Loop until leap_count reaches target_leap
# until loops while the condition is false:
# [ "$leap_count" -ge "$target_leap" ] is false (not yet reached) → keep looping
until [ "$leap_count" -ge "$target_leap" ]; do
leap_count=$((leap_count + 1))
echo " Time leap ${leap_count}: Hououin Kyouma has leapt into the past"
done
echo ""
echo "=== ${leap_count} time leaps complete. World line divergence observed ==="
echo ""
# -----------------------------------------------
# Demonstrate a file-wait pattern using until
# -----------------------------------------------
signal_file="/tmp/steinsgate_dmail.txt"
echo "=== Waiting for D-Mail ==="
echo ""
# Create the signal file in the background after 2 seconds
(sleep 2 && echo "El Psy Kongroo" > "${signal_file}") &
wait_count=0
# Poll every 0.5 seconds until the signal file appears
until [ -f "${signal_file}" ]; do
wait_count=$((wait_count + 1))
echo " Waiting... (check #${wait_count})"
sleep 0.5
done
echo ""
echo " D-Mail received: $(cat "${signal_file}")"
echo "=== World line has diverged ==="
# Clean up the temporary file
rm -f "${signal_file}"
$ chmod +x steinsgate_until.sh $ ./steinsgate_until.sh === Time Leap Sequence Start === Goal: observe world line divergence after 5 time leaps Time leap 1: Hououin Kyouma has leapt into the past Time leap 2: Hououin Kyouma has leapt into the past Time leap 3: Hououin Kyouma has leapt into the past Time leap 4: Hououin Kyouma has leapt into the past Time leap 5: Hououin Kyouma has leapt into the past === 5 time leaps complete. World line divergence observed === === Waiting for D-Mail === Waiting... (check #1) Waiting... (check #2) Waiting... (check #3) Waiting... (check #4) D-Mail received: El Psy Kongroo === World line has diverged ===
steinsgate_infinite_loop.sh
#!/bin/bash
# -----------------------------------------------
# Demonstrates infinite loop (while true)
# using Steins;Gate characters
# -----------------------------------------------
# -----------------------------------------------
# Implement a lab member selection menu
# using an infinite loop
# -----------------------------------------------
members=("Hououin Kyouma" "Assistant" "Mayuri" "Daru" "Kiryuu Moeka")
echo "=== Future Gadget Laboratory - Lab Member Lookup System ==="
echo ""
# Start an infinite loop with while true
# The menu repeats until the user enters 0
while true; do
echo "--- Menu ---"
# Dynamically generate menu items using a for loop
for ((j=0; j<${#members[@]}; j++)); do
echo " $((j + 1)). ${members[$j]}"
done
echo " 0. Exit"
echo ""
printf "Enter a number: "
read -r choice
# If the user enters 0, break out of the loop
if [ "$choice" = "0" ]; then
echo ""
echo "Exiting system. El Psy Kongroo."
break
fi
# Check whether the input is in the range 1-5
if [ "$choice" -ge 1 ] && [ "$choice" -le "${#members[@]}" ] 2>/dev/null; then
index=$((choice - 1))
echo ""
echo " Lab Member No.$(printf '%03d' "$choice"): ${members[$index]}"
echo ""
else
# For out-of-range input, use continue to show the menu again
echo ""
echo " Invalid input. Please enter a number from 1 to ${#members[@]}, or 0."
echo ""
continue
fi
done
$ chmod +x steinsgate_infinite_loop.sh $ ./steinsgate_infinite_loop.sh === Future Gadget Laboratory - Lab Member Lookup System === --- Menu --- 1. Hououin Kyouma 2. Assistant 3. Mayuri 4. Daru 5. Kiryuu Moeka 0. Exit Enter a number: 3 Lab Member No.003: Mayuri --- Menu --- 1. Hououin Kyouma 2. Assistant 3. Mayuri 4. Daru 5. Kiryuu Moeka 0. Exit Enter a number: 0 Exiting system. El Psy Kongroo.
steinsgate_while_read.sh
#!/bin/bash
# -----------------------------------------------
# Demonstrates line-by-line reading with while read
# using Steins;Gate characters
# -----------------------------------------------
# -----------------------------------------------
# Create a lab member data file
# -----------------------------------------------
data_file="/tmp/steinsgate_members.tsv"
# Write lab member data as tab-separated values
# Format: lab_number[TAB]codename[TAB]real_name
cat > "${data_file}" <<'EOF'
001 Hououin Kyouma Okabe Rintaro
002 Assistant Makise Kurisu
003 Mayuri Shiina Mayuri
004 Daru Hashida Itaru
005 Kiryuu Moeka Kiryuu Moeka
EOF
echo "=== Loading Lab Member Data ==="
echo ""
# while IFS=$'\t' read -r splits each tab-delimited line into multiple variables
# IFS=$'\t' sets the tab character as the delimiter
# -r treats backslashes as literal characters
# < "${data_file}" passes the file as standard input
while IFS=$'\t' read -r code codename realname; do
printf " No.%s Codename: %-18s Real name: %s\n" \
"${code}" "${codename}" "${realname}"
done < "${data_file}"
echo ""
echo "=== Example: reading command output via pipe ==="
echo ""
# Pipe grep output into while read
# Because the pipeline runs in a subshell,
# variables modified inside the loop are not inherited by the parent shell
count=0
grep -v "^004" "${data_file}" | while IFS=$'\t' read -r code codename realname; do
count=$((count + 1))
echo " Entry ${count}: ${codename} (${realname})"
done
echo ""
echo "Note: the count variable inside the pipe is not inherited by the parent shell"
echo " count (parent shell) = ${count} <- remains 0"
# Clean up the temporary file
rm -f "${data_file}"
$ chmod +x steinsgate_while_read.sh $ ./steinsgate_while_read.sh === Loading Lab Member Data === No.001 Codename: Hououin Kyouma Real name: Okabe Rintaro No.002 Codename: Assistant Real name: Makise Kurisu No.003 Codename: Mayuri Real name: Shiina Mayuri No.004 Codename: Daru Real name: Hashida Itaru No.005 Codename: Kiryuu Moeka Real name: Kiryuu Moeka === Example: reading command output via pipe === Entry 1: Hououin Kyouma (Okabe Rintaro) Entry 2: Assistant (Makise Kurisu) Entry 3: Mayuri (Shiina Mayuri) Entry 5: Kiryuu Moeka (Kiryuu Moeka) Note: the count variable inside the pipe is not inherited by the parent shell count (parent shell) = 0 <- remains 0
Summary
In shell scripts, while loops while its condition is true and until loops while its condition is false. For counter-controlled loops, combining while [ "$i" -lt n ] with i=$((i + 1)) works in all environments including POSIX sh. An infinite loop with while true is used together with break for interactive menus and polling. To read a file line by line, the standard form is while IFS= read -r line; do ... done < file, where IFS= prevents whitespace trimming and -r treats backslashes as literal characters. When piping command output into a loop, be aware that the loop runs in a subshell, so variables modified inside the loop are not inherited by the parent shell. For conditional branching inside a loop, see if statement. For other iteration patterns, see for statement.
If you find any errors or copyright issues, please contact us.