Advanced Bash-Scripting Guide:
PrevChapter 11. Loops and BranchesNext

11.3. Loop Control

Tournez cent tours, tournez mille tours,

Tournez souvent et tournez toujours . . .

--Verlaine, "Chevaux de bois"

Commands affecting loop behavior

break, continue

The break and continue loop control commands [1] correspond exactly to their counterparts in other programming languages. The break command terminates the loop (breaks out of it), while continue causes a jump to the next iteration of the loop, skipping all the remaining commands in that particular loop cycle.

Example 11-21. Effects of break and continue in a loop

#!/bin/bash
LIMIT=19 # Upper limit
echo
echo "Printing Numbers 1 through 20 (but not 3 and 11)."
a=0
while [ $a -le "$LIMIT" ]
do
 a=$(($a+1))
 if [ "$a" -eq 3 ] || [ "$a" -eq 11 ] # Excludes 3 and 11.
 then
 continue # Skip rest of this particular loop iteration.
 fi
 echo -n "$a " # This will not execute for 3 and 11.
done 
# Exercise:
# Why does the loop print up to 20?
echo; echo
echo Printing Numbers 1 through 20, but something happens after 2.
##################################################################
# Same loop, but substituting 'break' for 'continue'.
a=0
while [ "$a" -le "$LIMIT" ]
do
 a=$(($a+1))
 if [ "$a" -gt 2 ]
 then
 break # Skip entire rest of loop.
 fi
 echo -n "$a "
done
echo; echo; echo
exit 0

The break command may optionally take a parameter. A plain break terminates only the innermost loop in which it is embedded, but a break N breaks out of N levels of loop.

Example 11-22. Breaking out of multiple loop levels

#!/bin/bash
# break-levels.sh: Breaking out of loops.
# "break N" breaks out of N level loops.
for outerloop in 1 2 3 4 5
do
 echo -n "Group $outerloop: "
 # --------------------------------------------------------
 for innerloop in 1 2 3 4 5
 do
 echo -n "$innerloop "
 if [ "$innerloop" -eq 3 ]
 then
 break # Try break 2 to see what happens.
 # ("Breaks" out of both inner and outer loops.)
 fi
 done
 # --------------------------------------------------------
 echo
done 
echo
exit 0

The continue command, similar to break, optionally takes a parameter. A plain continue cuts short the current iteration within its loop and begins the next. A continue N terminates all remaining iterations at its loop level and continues with the next iteration at the loop, N levels above.

Example 11-23. Continuing at a higher loop level

#!/bin/bash
# The "continue N" command, continuing at the Nth level loop.
for outer in I II III IV V # outer loop
do
 echo; echo -n "Group $outer: "
 # --------------------------------------------------------------------
 for inner in 1 2 3 4 5 6 7 8 9 10 # inner loop
 do
 if [[ "$inner" -eq 7 && "$outer" = "III" ]]
 then
 continue 2 # Continue at loop on 2nd level, that is "outer loop".
 # Replace above line with a simple "continue"
 # to see normal loop behavior.
 fi 
 echo -n "$inner " # 7 8 9 10 will not echo on "Group III."
 done 
 # --------------------------------------------------------------------
done
echo; echo
# Exercise:
# Come up with a meaningful use for "continue N" in a script.
exit 0

Example 11-24. Using continue N in an actual task

# Albert Reiner gives an example of how to use "continue N":
# ---------------------------------------------------------
# Suppose I have a large number of jobs that need to be run, with
#+ any data that is to be treated in files of a given name pattern
#+ in a directory. There are several machines that access
#+ this directory, and I want to distribute the work over these
#+ different boxen.
# Then I usually nohup something like the following on every box:
while true
do
 for n in .iso.*
 do
 [ "$n" = ".iso.opts" ] && continue
 beta=${n#.iso.}
 [ -r .Iso.$beta ] && continue
 [ -r .lock.$beta ] && sleep 10 && continue
 lockfile -r0 .lock.$beta || continue
 echo -n "$beta: " `date`
 run-isotherm $beta
 date
 ls -alF .Iso.$beta
 [ -r .Iso.$beta ] && rm -f .lock.$beta
 continue 2
 done
 break
done
exit 0
# The details, in particular the sleep N, are particular to my
#+ application, but the general pattern is:
while true
do
 for job in {pattern}
 do
 {job already done or running} && continue
 {mark job as running, do job, mark job as done}
 continue 2
 done
 break # Or something like `sleep 600' to avoid termination.
done
# This way the script will stop only when there are no more jobs to do
#+ (including jobs that were added during runtime). Through the use
#+ of appropriate lockfiles it can be run on several machines
#+ concurrently without duplication of calculations [which run a couple
#+ of hours in my case, so I really want to avoid this]. Also, as search
#+ always starts again from the beginning, one can encode priorities in
#+ the file names. Of course, one could also do this without `continue 2',
#+ but then one would have to actually check whether or not some job
#+ was done (so that we should immediately look for the next job) or not
#+ (in which case we terminate or sleep for a long time before checking
#+ for a new job).
Caution

The continue N construct is difficult to understand and tricky to use in any meaningful context. It is probably best avoided.

Notes

[1]

These are shell builtins, whereas other loop commands, such as while and case, are keywords.


PrevHomeNext
Nested LoopsUpTesting and Branching

AltStyle によって変換されたページ (->オリジナル) /