# 11.3 循環控制
> Tournez cent tours, tournez mille tours,
>
> Tournez souvent et tournez toujours . . .
>
> ——保爾·魏爾倫,《木馬》
本節介紹兩個會影響循環行為的命令。
### break, continue
`break` 和 `continue` 命令[^1]的作用和在其他編程語言中的作用一樣。`break` 用來中止(跳出)循環,而 `continue` 則是略過未執行的循環部分,直接進行下一次循環。
樣例 11-21. 循環中 `break` 與 `continue` 的作用
```bash
#!/bin/bash
LIMIT=19 # 循環上界
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 ] # 除了 3 和 11。
then
continue # 略過本次循環的剩余部分。
fi
echo -n "$a " # 當 a 等于 3 和 11 時,將不會執行這條語句。
done
# 思考:
# 為什么循環不會輸出到20?
echo; echo
echo Printing Numbers 1 through 20, but something happens after 2.
##################################################################
# 用 'break' 代替了 'continue'。
a=0
while [ "$a" -le "$LIMIT" ]
do
a=$(($a+1))
if [ "$a" -gt 2 ]
then
break # 中止循環。
fi
echo -n "$a"
done
echo; echo; echo
exit 0
```
`break` 命令接受一個參數。普通的 `break` 命令僅僅跳出其所在的那層循環,而 `break N` 命令則可以跳出其上 N 層的循環。
樣例 11-22. 跳出多層循環
```bash
#!/bin/bash
# break-levels.sh: 跳出循環.
# "break N" 跳出 N 層循環。
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 # 嘗試一下 break 2 看看會發生什么。
# (它同時中止了內層和外層循環。)
fi
done
# ------------------------------------------
echo
done
echo
exit 0
```
與 `break` 類似,`continue` 也接受一個參數。普通的 `continue` 命令僅僅影響其所在的那層循環,而 `continue N` 命令則可以影響其上 N 層的循環。
樣例 11-23. `continue` 影響外層循環
```bash
#!/bin/bash
# "continue N" 命令可以影響其上 N 層循環。
for outer in I II III IV V # 外層循環
do
echo; echo -n "Group $outer: "
# --------------------------------------------------------------------
for inner in 1 2 3 4 5 6 7 8 9 10 # 內層循環
do
if [[ "$inner" -eq 7 && "$outer" = "III" ]]
then
continue 2 # 影響兩層循環,包括“外層循環”。
# 將其替換為普通的 "continue",那么只會影響內層循環。
fi
echo -n "$inner " # 7 8 9 10 將不會出現在 "Group III."中。
done
# --------------------------------------------------------------------
done
echo; echo
# 思考:
# 想一個 "continue N" 在腳本中的實際應用情況。
exit 0
```
樣例 11-24. 真實環境中的 `continue N`
```bash
# Albert Reiner 舉出了一個如何使用 "continue N" 的例子:
# ---------------------------------------------------
# 如果我有許多任務需要運行,并且運行所需要的數據都以文件的形
#+ 式存在文件夾中。現在有多臺設備可以訪問這個文件夾,我想將任
#+ 務分配給這些不同的設備來完成。
# 那么我通常會在每臺設備上執行下面的代碼:
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 -rf .lock.$beta
continue 2
done
break
done
exit 0
# 這個腳本中出現的 sleep N 只針對這個腳本,通常的形式是:
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 # 或者使用類似 `sleep 600` 這樣的語句來防止腳本結束。
done
# 這樣做可以保證腳本只會在沒有任務時(包括在運行過程中添加的任務)
#+ 才會停止。合理使用文件鎖保證多臺設備可以無重復的并行執行任務(這
#+ 在我的設備上通常會消耗好幾個小時,所以我想避免重復計算)。并且,
#+ 因為每次總是從頭開始搜索文件,因此可以通過文件名決定執行的先后
#+ 順序。當然,你可以不使用 'continue 2' 來完成這些,但是你必須
#+ 添加代碼去檢測某項任務是否完成(以此判斷是否可以執行下一項任務或
#+ 終止、休眠一段時間再執行下一項任務)。
```
>  `continue N` 結構不易理解并且可能在一些情況下有歧義,因此不建議使用。
[^1]: 這兩個命令是 [內建命令](http://tldp.org/LDP/abs/html/internal.html#BUILTINREF),而另外的循環命令,如 [`while`](http://tldp.org/LDP/abs/html/loops1.html#WHILELOOPREF) 和 [`case`](http://tldp.org/LDP/abs/html/testbranch.html#CASEESAC1) 則是 [關鍵詞](http://tldp.org/LDP/abs/html/internal.html#KEYWORDREF)。
- 第一部分 初見shell
- 1. 為什么使用shell編程
- 2. 和Sha-Bang(#!)一起出發
- 2.1 調用一個腳本
- 2.2 牛刀小試
- 第二部分 shell基礎
- 3. 特殊字符
- 4. 變量與參數
- 4.1 變量替換
- 4.2 變量賦值
- 4.3 Bash弱類型變量
- 4.4 特殊變量類型
- 5. 引用
- 5.1 引用變量
- 5.2 轉義
- 6. 退出與退出狀態
- 7. 測試
- 7.1 測試結構
- 7.2 文件測試操作
- 7.3 其他比較操作
- 7.4 嵌套 if/then 條件測試
- 7.5 牛刀小試
- 8. 運算符相關話題
- 8.1 運算符
- 8.2 數字常量
- 8.3 雙圓括號結構
- 8.4 運算符優先級
- 第三部分 shell進階
- 10. 變量處理
- 10.1 字符串處理
- 10.1.1 使用 awk 處理字符串
- 10.1.2 參考資料
- 10.2 參數替換
- 11. 循環與分支
- 11.1 循環
- 11.2 嵌套循環
- 11.3 循環控制
- 11.4 測試與分支
- 12. 命令替換
- 13. 算術擴展
- 14. 休息時間
- 第五部分 進階話題
- 19. 嵌入文檔
- 20. I/O 重定向
- 20.1 使用 exec
- 20.2 重定向代碼塊
- 20.3 應用程序
- 22. 限制模式的Shell
- 23. 進程替換
- 26. 列表結構
- 25. 別名