Loop Control

Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting
Prev | Chapter 11. Loops and Branches | Next

# 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

         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.

### Notes

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

Prev | Home | Next
Nested Loops | Up | Testing and Branching