Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting | ||
---|---|---|
Prev | Chapter 11. Loops and Branches | Next |
Tournez cent tours, tournez mille tours, Tournez souvent et tournez toujours . . . --Verlaine, "Chevaux de bois" |
Commands affecting loop behavior
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
1 #!/bin/bash 2 3 LIMIT=19 # Upper limit 4 5 echo 6 echo "Printing Numbers 1 through 20 (but not 3 and 11)." 7 8 a=0 9 10 while [ $a -le "$LIMIT" ] 11 do 12 a=$(($a+1)) 13 14 if [ "$a" -eq 3 ] || [ "$a" -eq 11 ] # Excludes 3 and 11. 15 then 16 continue # Skip rest of this particular loop iteration. 17 fi 18 19 echo -n "$a " # This will not execute for 3 and 11. 20 done 21 22 # Exercise: 23 # Why does the loop print up to 20? 24 25 echo; echo 26 27 echo Printing Numbers 1 through 20, but something happens after 2. 28 29 ################################################################## 30 31 # Same loop, but substituting 'break' for 'continue'. 32 33 a=0 34 35 while [ "$a" -le "$LIMIT" ] 36 do 37 a=$(($a+1)) 38 39 if [ "$a" -gt 2 ] 40 then 41 break # Skip entire rest of loop. 42 fi 43 44 echo -n "$a " 45 done 46 47 echo; echo; echo 48 49 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
1 #!/bin/bash 2 # break-levels.sh: Breaking out of loops. 3 4 # "break N" breaks out of N level loops. 5 6 for outerloop in 1 2 3 4 5 7 do 8 echo -n "Group $outerloop: " 9 10 # -------------------------------------------------------- 11 for innerloop in 1 2 3 4 5 12 do 13 echo -n "$innerloop " 14 15 if [ "$innerloop" -eq 3 ] 16 then 17 break # Try break 2 to see what happens. 18 # ("Breaks" out of both inner and outer loops.) 19 fi 20 done 21 # -------------------------------------------------------- 22 23 echo 24 done 25 26 echo 27 28 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
1 #!/bin/bash 2 # The "continue N" command, continuing at the Nth level loop. 3 4 for outer in I II III IV V # outer loop 5 do 6 echo; echo -n "Group $outer: " 7 8 # -------------------------------------------------------------------- 9 for inner in 1 2 3 4 5 6 7 8 9 10 # inner loop 10 do 11 12 if [[ "$inner" -eq 7 && "$outer" = "III" ]] 13 then 14 continue 2 # Continue at loop on 2nd level, that is "outer loop". 15 # Replace above line with a simple "continue" 16 # to see normal loop behavior. 17 fi 18 19 echo -n "$inner " # 7 8 9 10 will not echo on "Group III." 20 done 21 # -------------------------------------------------------------------- 22 23 done 24 25 echo; echo 26 27 # Exercise: 28 # Come up with a meaningful use for "continue N" in a script. 29 30 exit 0 |
Example 11-24. Using continue N in an actual task
1 # Albert Reiner gives an example of how to use "continue N": 2 # --------------------------------------------------------- 3 4 # Suppose I have a large number of jobs that need to be run, with 5 #+ any data that is to be treated in files of a given name pattern 6 #+ in a directory. There are several machines that access 7 #+ this directory, and I want to distribute the work over these 8 #+ different boxen. 9 # Then I usually nohup something like the following on every box: 10 11 while true 12 do 13 for n in .iso.* 14 do 15 [ "$n" = ".iso.opts" ] && continue 16 beta=${n#.iso.} 17 [ -r .Iso.$beta ] && continue 18 [ -r .lock.$beta ] && sleep 10 && continue 19 lockfile -r0 .lock.$beta || continue 20 echo -n "$beta: " `date` 21 run-isotherm $beta 22 date 23 ls -alF .Iso.$beta 24 [ -r .Iso.$beta ] && rm -f .lock.$beta 25 continue 2 26 done 27 break 28 done 29 30 exit 0 31 32 # The details, in particular the sleep N, are particular to my 33 #+ application, but the general pattern is: 34 35 while true 36 do 37 for job in {pattern} 38 do 39 {job already done or running} && continue 40 {mark job as running, do job, mark job as done} 41 continue 2 42 done 43 break # Or something like `sleep 600' to avoid termination. 44 done 45 46 # This way the script will stop only when there are no more jobs to do 47 #+ (including jobs that were added during runtime). Through the use 48 #+ of appropriate lockfiles it can be run on several machines 49 #+ concurrently without duplication of calculations [which run a couple 50 #+ of hours in my case, so I really want to avoid this]. Also, as search 51 #+ always starts again from the beginning, one can encode priorities in 52 #+ the file names. Of course, one could also do this without `continue 2', 53 #+ but then one would have to actually check whether or not some job 54 #+ was done (so that we should immediately look for the next job) or not 55 #+ (in which case we terminate or sleep for a long time before checking 56 #+ for a new job). |
The continue N construct is difficult to understand and tricky to use in any meaningful context. It is probably best avoided. |
[1] | These are shell builtins, whereas other loop commands, such as while and case, are keywords. |