11.3 循环控制

Tournez cent tours, tournez mille tours,

Tournez souvent et tournez toujours . . .

——保尔·魏尔伦,《木马》

本节介绍两个会影响循环行为的命令。

break, continue

breakcontinue 命令[^1]的作用和在其他编程语言中的作用一样。break 用来中止(跳出)循环,而 continue 则是略过未执行的循环部分,直接进行下一次循环。

样例 11-21. 循环中 breakcontinue 的作用

  1. #!/bin/bash
  2. LIMIT=19 # 循环上界
  3. echo
  4. echo "Printing Numbers 1 through 20 (but not 3 and 11)."
  5. a=0
  6. while [ $a -le "$LIMIT" ]
  7. do
  8. a=$(($a+1))
  9. if [ "$a" -eq 3 ] || [ "$a" -eq 11 ] # 除了 3 和 11。
  10. then
  11. continue # 略过本次循环的剩余部分。
  12. fi
  13. echo -n "$a " # 当 a 等于 3 和 11 时,将不会执行这条语句。
  14. done
  15. # 思考:
  16. # 为什么循环不会输出到20?
  17. echo; echo
  18. echo Printing Numbers 1 through 20, but something happens after 2.
  19. ##################################################################
  20. # 用 'break' 代替了 'continue'。
  21. a=0
  22. while [ "$a" -le "$LIMIT" ]
  23. do
  24. a=$(($a+1))
  25. if [ "$a" -gt 2 ]
  26. then
  27. break # 中止循环。
  28. fi
  29. echo -n "$a"
  30. done
  31. echo; echo; echo
  32. exit 0

break 命令接受一个参数。普通的 break 命令仅仅跳出其所在的那层循环,而 break N 命令则可以跳出其上 N 层的循环。

样例 11-22. 跳出多层循环

  1. #!/bin/bash
  2. # break-levels.sh: 跳出循环.
  3. # "break N" 跳出 N 层循环。
  4. for outerloop in 1 2 3 4 5
  5. do
  6. echo -n "Group $outerloop: "
  7. # ------------------------------------------
  8. for innerloop in 1 2 3 4 5
  9. do
  10. echo -n "$innerloop "
  11. if [ "$innerloop" -eq 3 ]
  12. then
  13. break # 尝试一下 break 2 看看会发生什么。
  14. # (它同时中止了内层和外层循环。)
  15. fi
  16. done
  17. # ------------------------------------------
  18. echo
  19. done
  20. echo
  21. exit 0

break 类似,continue 也接受一个参数。普通的 continue 命令仅仅影响其所在的那层循环,而 continue N 命令则可以影响其上 N 层的循环。

样例 11-23. continue 影响外层循环

  1. #!/bin/bash
  2. # "continue N" 命令可以影响其上 N 层循环。
  3. for outer in I II III IV V # 外层循环
  4. do
  5. echo; echo -n "Group $outer: "
  6. # --------------------------------------------------------------------
  7. for inner in 1 2 3 4 5 6 7 8 9 10 # 内层循环
  8. do
  9. if [[ "$inner" -eq 7 && "$outer" = "III" ]]
  10. then
  11. continue 2 # 影响两层循环,包括“外层循环”。
  12. # 将其替换为普通的 "continue",那么只会影响内层循环。
  13. fi
  14. echo -n "$inner " # 7 8 9 10 将不会出现在 "Group III."中。
  15. done
  16. # --------------------------------------------------------------------
  17. done
  18. echo; echo
  19. # 思考:
  20. # 想一个 "continue N" 在脚本中的实际应用情况。
  21. exit 0

样例 11-24. 真实环境中的 continue N

  1. # Albert Reiner 举出了一个如何使用 "continue N" 的例子:
  2. # ---------------------------------------------------
  3. # 如果我有许多任务需要运行,并且运行所需要的数据都以文件的形
  4. #+ 式存在文件夹中。现在有多台设备可以访问这个文件夹,我想将任
  5. #+ 务分配给这些不同的设备来完成。
  6. # 那么我通常会在每台设备上执行下面的代码:
  7. while true:
  8. do
  9. for n in .iso.*
  10. do
  11. [ "$n" = ".iso.opts" ] && continue
  12. beta=${n#.iso.}
  13. [ -r .Iso.$beta ] && continue
  14. [ -r .lock.$beta ] && sleep 10 && continue
  15. lockfile -r0 .lock.$beta || continue
  16. echo -n "$beta: " `date`
  17. run-isotherm $beta
  18. date
  19. ls -alF .Iso.$beta
  20. [ -r .Iso.$beta ] && rm -rf .lock.$beta
  21. continue 2
  22. done
  23. break
  24. done
  25. exit 0
  26. # 这个脚本中出现的 sleep N 只针对这个脚本,通常的形式是:
  27. while true
  28. do
  29. for job in {pattern}
  30. do
  31. {job already done or running} && continue
  32. {mark job as running, do job, mark job as done}
  33. continue 2
  34. done
  35. break # 或者使用类似 `sleep 600` 这样的语句来防止脚本结束。
  36. done
  37. # 这样做可以保证脚本只会在没有任务时(包括在运行过程中添加的任务)
  38. #+ 才会停止。合理使用文件锁保证多台设备可以无重复的并行执行任务(这
  39. #+ 在我的设备上通常会消耗好几个小时,所以我想避免重复计算)。并且,
  40. #+ 因为每次总是从头开始搜索文件,因此可以通过文件名决定执行的先后
  41. #+ 顺序。当然,你可以不使用 'continue 2' 来完成这些,但是你必须
  42. #+ 添加代码去检测某项任务是否完成(以此判断是否可以执行下一项任务或
  43. #+ 终止、休眠一段时间再执行下一项任务)。

caution continue N 结构不易理解并且可能在一些情况下有歧义,因此不建议使用。

[^1]: 这两个命令是 内建命令,而另外的循环命令,如 whilecase 则是 关键词