| |

LFCS 124 ๐Ÿง Shell Scripting โ€” Loops

โฑ๏ธ Estimated read: 11 min | |

The previous chapter covered conditionals: the if statement, the test command, and the case statement. This chapter covers loops: for, while, and until. These are the constructs that repeat a block of commands. The LFCS exam tests the syntax of the for loop over a list and over a range, the while loop with a condition, the until loop with an inverted condition, and the loop control commands break and continue. The critical trap is the loop that never terminates: a while loop with a condition that never becomes false, or a for loop that re-reads a file without changing the pointer, or a loop that consumes arguments with shift but forgets to terminate when the arguments run out.

Key point: The for loop iterates over a list of words. The list can be literal (for i in 1 2 3), a range (for i in {1..10}), a command substitution (for f in $(ls)), or the positional parameters (for arg in "$@"). The while loop runs as long as the condition returns zero (true). The until loop runs as long as the condition returns non-zero (false). The break command exits the loop entirely. The continue command skips the rest of the current iteration and proceeds to the next.


Why loops require a termination guarantee

The infinite loop problem. A while loop runs until its condition is false. If the condition is always true, the loop never terminates. The classic mistake is while [ "$input" != "quit" ] without a read inside the loop to update the variable. The fix is to ensure that every iteration modifies a variable that the condition depends on, or to add a counter that limits the iterations.

The word-splitting problem. A for loop iterates over words, not lines. The for line in $(cat file) form splits the file’s contents on whitespace, not on newlines. A line with spaces becomes multiple iterations. The fix is while IFS= read -r line for line-by-line iteration, or for file in *.txt for filename iteration.

The subshell problem. A while loop that reads from a pipe (cat file | while read line) runs in a subshell. Variables set inside the loop do not persist after the loop. The fix is to use process substitution (while read line; do ...; done < file) or a here-string (while read line; do ...; done <<< "$data").

The break versus continue problem. The break command exits the loop. The continue command skips the rest of the current iteration but stays in the loop. The distinction matters in nested loops: break 2 exits two levels, continue 2 skips to the next iteration of the outer loop.

The trade-off. The for loop is for a known set of items. The while loop is for an unknown number of iterations, determined by a condition. The until loop is the same as while with the condition negated, which is useful when the “true” case is the one to exit on. The for loop is the most common; the while loop is for reading input; the until loop is the least common but is the correct choice for “keep trying until success”.


a. The for loop: lists, ranges, and files

The for loop assigns each item in the list to the loop variable and runs the body. The list is expanded before the loop starts.

#!/bin/bash
for name in alice bob charlie; do
  echo "Hello, $name"
done

The list can be a brace expansion for a numeric or character range.

# Numeric range
for i in {1..5}; do
  echo "Number: $i"
done

# Numeric range with step
for i in {0..10..2}; do
  echo "Even: $i"
done

# Character range
for letter in {a..e}; do
  echo "Letter: $letter"
done

The brace expansion is a bash feature. In POSIX shell, the equivalent is a while loop with an arithmetic counter, or the seq command: for i in $(seq 1 5).

The for loop over filenames is the correct way to process files. The glob expansion is performed by the shell before the loop starts.

for file in *.txt; do
  echo "Processing $file"
  # Process the file
done

If no files match the glob, the loop runs once with the literal string *.txt. The nullglob shell option changes this behavior: with shopt -s nullglob, the glob expands to nothing, and the loop does not run.

The C-style for loop is a bash extension. It is useful for iteration with an arbitrary step or complex initialization.

for ((i = 0; i < 10; i++)); do
  echo "Iteration: $i"
done

b. The while and until loops: conditions

The while loop runs as long as the condition returns zero. The until loop runs as long as the condition returns non-zero. The two are equivalent, with the condition negated.

#!/bin/bash
count=1
while [ "$count" -le 5 ]; do
  echo "Count: $count"
  count=$((count + 1))
done

The count=$((count + 1)) increments the counter. Without it, the condition [ "$count" -le 5 ] is always true, and the loop is infinite.

The while loop with read is the standard pattern for processing a file line by line.

#!/bin/bash
while IFS= read -r line; do
  echo "Line: $line"
done < file.txt

The IFS= clears the internal field separator, which prevents leading and trailing whitespace from being trimmed. The -r flag prevents backslash escapes from being interpreted. The < file.txt redirects the file into the loop, which runs in the current shell, not a subshell.

The until loop runs until the condition is true. It is the correct choice for “retry until success”.

#!/bin/bash
attempt=0
until curl -sf https://example.com/health; do
  attempt=$((attempt + 1))
  if [ "$attempt" -ge 5 ]; then
    echo "Failed after 5 attempts" >&2
    exit 1
  fi
  sleep 2
done
echo "Service is healthy"

The loop runs the curl command. If it fails, the loop increments the attempt counter and waits. If it succeeds, the loop exits. The attempt counter prevents an infinite loop.


c. The break and continue commands

The break command exits the loop. The continue command skips to the next iteration.

#!/bin/bash
for i in {1..10}; do
  if [ "$i" -eq 5 ]; then
    break
  fi
  echo "Number: $i"
done
# Output: 1, 2, 3, 4

The break exits the loop when i is 5. The numbers 6 through 10 are not printed.

#!/bin/bash
for i in {1..5}; do
  if [ "$i" -eq 3 ]; then
    continue
  fi
  echo "Number: $i"
done
# Output: 1, 2, 4, 5

The continue skips the echo when i is 3. The loop continues with 4.

The break N and continue N forms work in nested loops. The N is the number of levels to break or continue.

for i in {1..3}; do
  for j in {1..3}; do
    if [ "$j" -eq 2 ]; then
      break 2
    fi
    echo "$i,$j"
  done
done
# Output: 1,1

The break 2 exits both the inner and outer loops. The output is only 1,1.


Complete Example Session

# ============================================
# PART 1: FOR LOOP OVER LIST
# ============================================

cat > list.sh <<'EOF'
#!/bin/bash
for name in alice bob charlie; do
  echo "Hello, $name"
done
EOF
chmod +x list.sh
./list.sh
# Output:
# Hello, alice
# Hello, bob
# Hello, charlie
# ============================================
# PART 2: FOR LOOP OVER RANGE
# ============================================

cat > range.sh <<'EOF'
#!/bin/bash
for i in {1..5}; do
  echo "Number: $i"
done
EOF
chmod +x range.sh
./range.sh
# Output: 1, 2, 3, 4, 5
# ============================================
# PART 3: FOR LOOP OVER FILES
# ============================================

for file in *.txt; do
  echo "File: $file"
done
# Lists all .txt files.
# ============================================
# PART 4: WHILE LOOP
# ============================================

cat > count.sh <<'EOF'
#!/bin/bash
count=1
while [ "$count" -le 5 ]; do
  echo "Count: $count"
  count=$((count + 1))
done
EOF
chmod +x count.sh
./count.sh
# Output: 1, 2, 3, 4, 5
# ============================================
# PART 5: WHILE READ
# ============================================

cat > read.sh <<'EOF'
#!/bin/bash
while IFS= read -r line; do
  echo "Line: $line"
done < "$1"
EOF
chmod +x read.sh
./read.sh /etc/hostname
# Output: Line: <hostname>
# ============================================
# PART 6: UNTIL LOOP
# ============================================

cat > until.sh <<'EOF'
#!/bin/bash
count=1
until [ "$count" -gt 5 ]; do
  echo "Count: $count"
  count=$((count + 1))
done
EOF
chmod +x until.sh
./until.sh
# Output: 1, 2, 3, 4, 5
# ============================================
# PART 7: BREAK
# ============================================

cat > brk.sh <<'EOF'
#!/bin/bash
for i in {1..10}; do
  if [ "$i" -eq 5 ]; then
    break
  fi
  echo "$i"
done
EOF
chmod +x brk.sh
./brk.sh
# Output: 1, 2, 3, 4
# ============================================
# PART 8: CONTINUE
# ============================================

cat > cont.sh <<'EOF'
#!/bin/bash
for i in {1..5}; do
  if [ "$i" -eq 3 ]; then
    continue
  fi
  echo "$i"
done
EOF
chmod +x cont.sh
./cont.sh
# Output: 1, 2, 4, 5
# ============================================
# PART 9: NESTED BREAK
# ============================================

cat > nested.sh <<'EOF'
#!/bin/bash
for i in {1..3}; do
  for j in {1..3}; do
    if [ "$j" -eq 2 ]; then
      break 2
    fi
    echo "$i,$j"
  done
done
EOF
chmod +x nested.sh
./nested.sh
# Output: 1,1

The nine parts trace the loops: for over list, for over range, for over files, while, while read, until, break, continue, and break 2.


Quick Reference

Loop Types

LoopConditionUse Case
forList of itemsKnown set
whileCommand succeedsUnknown count
untilCommand failsRetry until success
for (( ))C-style expressionCounter with step

Loop Control

CommandEffect
breakExit loop
break NExit N levels
continueSkip iteration
continue NSkip to Nth loop

Read Patterns

PatternPurpose
while read lineLine iteration
while IFS= read -r linePreserve whitespace and backslashes
while read -r line < fileRead from file

Common Ranges

RangeMeaning
{1..5}1 through 5
{0..10..2}0, 2, 4, 6, 8, 10
{a..e}a through e
seq 1 5POSIX alternative

Best Practices

โœ… Do This:

while IFS= read -r line; do
  echo "$line"
done < file.txt
<!-- Preserve whitespace and backslashes -->          # โœ…
for arg in "$@"; do
  echo "$arg"
done
<!-- Quote positional parameters -->                 # โœ…
for i in {1..10}; do
  if [ "$i" -eq 5 ]; then break; fi
done
<!-- Explicit break -->                              # โœ…

โŒ Don’t Do This:

for line in $(cat file.txt); do
<!-- Splits on whitespace, not newlines -->          # โŒ
while [ "$x" != "quit" ]; do
  echo "$x"
done
<!-- x never changes, infinite loop -->              # โŒ
cat file.txt | while read line; do
<!-- Subshell: variables lost after loop -->         # โŒ

Common Pitfalls

PitfallWhy It HappensFix
Infinite loopCondition never falseUpdate the variable
Filename with spaces failsUnquoted $file"$file"
Lines with spaces split$(cat file)while read
Variables lost after loopPipe subshell< file redirect
Glob runs once if no filesNo nullglobshopt -s nullglob

Real-World Examples

1. For Over List

for name in alice bob; do echo "$name"; done

2. For Over Range

for i in {1..5}; do echo "$i"; done

3. For Over Files

for f in *.txt; do echo "$f"; done

4. C-Style For

for ((i=0; i<10; i++)); do echo "$i"; done

5. While Counter

while [ "$count" -le 5 ]; do count=$((count+1)); done

6. While Read

while IFS= read -r line; do echo "$line"; done < file

7. Until Retry

until curl -sf https://api/health; do sleep 2; done

8. Break

for i in {1..10}; do if [ "$i" -eq 5 ]; then break; fi; done

9. Continue

for i in {1..10}; do if [ "$i" -eq 3 ]; then continue; fi; echo "$i"; done

10. Nested Break

for i in {1..3}; do for j in {1..3}; do break 2; done; done

Visual

for Loop Flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  for item in list                            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  More items?                                 โ”‚
โ”‚  โ”œโ”€ NO  โ†’ exit loop                          โ”‚
โ”‚  โ””โ”€ YES โ†’ run body with current item         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Next iteration                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

while vs until

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  while [ condition ]                         โ”‚
โ”‚  โ†’ Run while condition is true (exit 0)      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  until [ condition ]                         โ”‚
โ”‚  โ†’ Run while condition is false (exit != 0)  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
List iterationfor item in list
Range iterationfor i in {1..5}
C-stylefor ((i=0; i<10; i++))
Condition loopwhile [ condition ]
Inverse conditionuntil [ condition ]
Line readwhile IFS= read -r line
Exit loopbreak
Skip iterationcontinue
Nested controlbreak 2, continue 2

Key takeaways:

  • The for loop is for a known set of items. The list can be literal, a brace expansion, a glob, or positional parameters. The loop variable takes each item in turn.
  • The while and until loops are for conditions. The while loop runs while the condition is true. The until loop runs while the condition is false. Both require the condition to change inside the loop.
  • The while read pattern is the correct way to process files line by line. The IFS= and -r flags preserve whitespace and backslashes. The < file redirect keeps the loop in the current shell.
  • The break and continue commands control the flow. The break exits the loop. The continue skips to the next iteration. The break N and continue N forms work in nested loops.
  • The infinite loop is the primary failure mode. The condition must depend on a variable that the loop body modifies. A counter is the simplest guarantee.

Remember: The loop is the iteration construct. The for loop is for the known set. The while loop is for the condition. The until loop is for the retry. The break is the exit. The continue is the skip. The while read is the file reader. The exam tests the syntax and the termination condition.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!