Loop Control

Advanced Bash-Scripting Guide:
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

      #!/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).

  | 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