Classic Shell Scripting - Arnold Robbins [77]
The only difference between while and until is how the exit status of condition is treated. while continues to loop as long as condition exited successfully. until loops as long as condition exits unsuccessfully. For example:
pattern=... pattern controls shortening of string
while [ -n "$string" ] While string is not empty
do
process current value of $string
string=${string%$pattern} Lop off part of string
done
In practice, the until loop is used much less than the while loop, but it can be useful when you need to wait for an event to happen. This is shown in Example 6-2.
Example 6-2. Wait for a user to log in, using until
# wait for specified user to log in, check every 30 seconds
printf "Enter username: "
read user
until who | grep "$user" > /dev/null
do
sleep 30
done
It is possible to pipe into a while loop, for iterating over each line of input, as shown here:
generate data |
while read name rank serial_no
do
...
done
In such cases, the command used for the while loop's condition is usually the read command. We present a real-life example later in Section 7.3.1, when discussing here-documents. In Section 7.6, we show that you can also pipe the output of a loop into another command.
break and continue
Not everything in the shell came straight from Algol 68. The shell borrowed the break and continue commands from C. They are used to leave a loop, or to skip the rest of the loop body, respectively. The until...do wait-for-a-user script in Example 6-2 can be rewritten more conventionally, as shown here in Example 6-3.
Example 6-3. Wait for a user to log in, using while and break
# wait for specified user to log in, check every 30 seconds
printf "Enter username: "
read user
while true
do
if who | grep "$user" > /dev/null
then
break
fi
sleep 30
done
The true command does nothing but exit successfully. It's used for writing infinite loops—loops that run forever. When you write an infinite loop, you have to place an exit condition in the body of the loop, just as was done here. There is an analogous, but considerably less-used command, false, which does nothing, but it does so unsuccessfully. It would be used in an infinite until false ... loop.
The continue command is used to start the next iteration of a loop early, before reaching the bottom of a loop's body.
Both the break and the continue commands take an optional numeric argument. This indicates how many enclosing loops should be broken out of or continued. (Use $((...)) if the loop count needs to be an expression calculated at runtime.) For example:
while condition1 Outer loop
do ...
while condition2 Inner loop
do ...
break 2 Break out of outer loop
done
done
... Execution continues here after break
It is interesting to note that break and continue, particularly with the ability to break or continue multiple loop levels, compensate in a clean fashion for the absence of a goto keyword in the shell language.
shift and Option Processing
We briefly mentioned the shift command earlier, in Section 6.1.2.2. shift is used when working with command-line arguments. Its job is to move them left by one (or more). After executing shift, the original $1 is lost; it is replaced with the old value of $2. The new value of $2 is the old value of $3, and so on. The value of $# decreases each time, as well. shift accepts an optional argument, which is the number of places to shift by: the default is 1.
Simple option processing is often done by combining while, case, break, and shift, like so:
# set flag vars to empty
file= verbose= quiet= long=
while [ $# -gt 0 ] Loop until no args left
do
case $1 in Check first arg
-f) file=$2
shift Shift off "-f" so that shift at end gets value in $2
;;
-v) verbose=true
quiet=
;;
-q) quiet=true
verbose=
;;
-l) long=true
;;
--) shift By convention, - - ends options
break
;;
-*) echo $0: $1: unrecognized option >&2
;;
*) break Nonoption argument, break while loop
;;
esac
shift Set up for next iteration