Read

Module 9 · Shell scripting basics

Loops: while and until

Loop while a condition holds or until it does, count with a variable, read a file line by line and avoid the subshell trap that eats your totals.

What you will learn

  • Write `while` and `until` loops with a counter.
  • Read a file line by line with `while read -r line; do …; done < file`.
  • Explain why variables set inside `cmd | while read` are lost.

for needs its list up front. while does not: it runs a condition command, and as long as that command succeeds, it runs the body again. until is the mirror image, looping as long as the condition fails. Both end with done, and the condition is any command, usually [ ].

~% cat lab/l86/countdown.sh
#!/bin/sh
n=5
while [ "$n" -gt 0 ]; do
    echo "$n"
    n=$(( n - 1 ))
done
echo "Liftoff"
~% lab/l86/countdown.sh
5
4
3
2
1
Liftoff
~% n=0; until [ -e /tmp/ready ]; do n=$(( n + 1 )); [ "$n" -ge 3 ] && touch /tmp/ready; done; echo "waited $n turns"
waited 3 turns

Every while needs something in the body that eventually makes the condition false, here n=$(( n - 1 )). Forget it and the loop never ends; press Ctrl+C to kill it. Infinite loops on purpose are written while true; do …; done or while :; do …; done with a break inside, for example to retry a command until it works, with a sleep 1 between attempts so you do not burn the CPU.

Reading a file line by line

The most useful while in shell programming is while read -r line; do …; done < file. read takes one line from its input, stores it in line, and succeeds; at end of file it fails and the loop stops. The -r keeps backslashes literal. The redirection goes after done, so the whole loop reads from the file. Give read several names and the line is split on spaces: read -r name age puts the first word in name and the rest in age; set IFS=: in front to split on colons instead, handy for /etc/passwd.

~% cat lab/l86/numbers.txt
10
20
30
~% total=0
~% while read -r n; do total=$(( total + n )); done < lab/l86/numbers.txt
~% echo "total=$total"
total=60
~% total=0
~% cat lab/l86/numbers.txt | while read -r n; do total=$(( total + n )); done
~% echo "total=$total"
total=0

One more subtlety: read fails on the last line if the file does not end with a newline, so that line is silently skipped. Files you create with printf '…\n' or vi are fine; for files of unknown origin write while read -r l || [ -n "$l" ]; do to process the leftover.

LoopRuns whileTypical use
for x in listitems remainFiles, fixed lists, seq
while cmdcmd succeedsCounters, reading lines, retries
until cmdcmd failsWaiting for a file or a service

Commands in this lesson

CommandWhat it does
while [ cond ]; do …; doneRepeat while the test succeeds.
until [ cond ]; do …; doneRepeat until the test succeeds.
while read -r line; do …; done < fileProcess a file line by line.
IFS=: read -r a b cSplit the line on colons.
while true; do …; break; doneLoop until an explicit break.
sleep 1Pause between retries.

Quiz

  1. When does a `while` loop stop?

    • When its condition command fails (non-zero status)
    • After ten iterations
    • When the body prints something
  2. Where does the input redirection go in a line-reading loop?

    • After `read`: `while read -r l < file; do`
    • After `done`: `done < file`
    • After `while`: `while < file read -r l`
  3. `cat f | while read -r n; do t=$((t+n)); done; echo $t` prints an empty value. Why?

    • `read` cannot read numbers
    • The loop ran in a subshell because of the pipe, so `t` died with it
    • `$((…))` is not allowed inside loops
  4. What does `-r` do in `read -r line`?

    • Reads the file in reverse
    • Keeps backslashes literal instead of treating them as escapes
    • Removes the trailing newline

Practice

  1. Write an executable script `/root/lab/l86/countdown.sh` that uses a `while` loop to print 5, 4, 3, 2, 1 on separate lines and then `Liftoff`.

  2. Write an executable script `/root/lab/l86/hello.sh` that reads `/root/lab/l86/names.txt` line by line and prints `Hello, NAME` for each line.

  3. Write an executable script `/root/lab/l86/total.sh` that adds up the numbers in `/root/lab/l86/numbers.txt` (one per line) and prints only the total. Remember the subshell trap: feed the loop with `<`, not a pipe.

Open this lesson in the app to do the tasks in a real Linux machine and have them checked.