# 10.3. Loop Control

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 (repetition) of the loop, skipping all the remaining commands in that particular loop cycle.

Example 10-20. 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 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 10-21. 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 10-22. 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 ] 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 never echo. 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 10-23. 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 in a 6 #+ directory. There are several machines that access this directory, and 7 #+ I want to distribute the work over these different boxen. Then I 8 #+ usually nohup something like the following on every box: 9  10 while true 11 do 12  for n in .iso.* 13  do 14  [ "\$n" = ".iso.opts" ] && continue 15  beta=\${n#.iso.} 16  [ -r .Iso.\$beta ] && continue 17  [ -r .lock.\$beta ] && sleep 10 && continue 18  lockfile -r0 .lock.\$beta || continue 19  echo -n "\$beta: " `date` 20  run-isotherm \$beta 21  date 22  ls -alF .Iso.\$beta 23  [ -r .Iso.\$beta ] && rm -f .lock.\$beta 24  continue 2 25  done 26  break 27 done 28  29 # The details, in particular the sleep N, are particular to my 30 #+ application, but the general pattern is: 31  32 while true 33 do 34  for job in {pattern} 35  do 36  {job already done or running} && continue 37  {mark job as running, do job, mark job as done} 38  continue 2 39  done 40  break # Or something like `sleep 600' to avoid termination. 41 done 42  43 # This way the script will stop only when there are no more jobs to do 44 #+ (including jobs that were added during runtime). Through the use 45 #+ of appropriate lockfiles it can be run on several machines 46 #+ concurrently without duplication of calculations [which run a couple 47 #+ of hours in my case, so I really want to avoid this]. Also, as search 48 #+ always starts again from the beginning, one can encode priorities in 49 #+ the file names. Of course, one could also do this without `continue 2', 50 #+ but then one would have to actually check whether or not some job 51 #+ was done (so that we should immediately look for the next job) or not 52 #+ (in which case we terminate or sleep for a long time before checking 53 #+ 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.