在前面的章節中,我們開發了菜單驅動程序,來產生各種各樣的系統信息。雖然程序能夠運行, 但它仍然存在重大的可用問題。它只能執行單一的選擇,然后終止。更糟糕地是,如果做了一個 無效的選擇,程序會以錯誤終止,而沒有給用戶提供再試一次的機會。如果我們能構建程序, 以致于程序能夠重復顯示菜單,而且能一次由一次的選擇,直到用戶選擇退出程序,這樣的程序會更好一些。
在這一章中,我們將看一個叫做循環的程序概念,其可用來使程序的某些部分重復。shell 為循環提供了三個復合命令。 本章我們將查看其中的兩個命令,隨后章節介紹第三個命令。
## 循環
日常生活中充滿了重復性的活動。每天去散步,遛狗,切胡蘿卜,所有任務都要重復一系列的步驟。 讓我們以切胡蘿卜為例。如果我們用偽碼表達這種活動,它可能看起來像這樣:
1. 準備切菜板
2. 準備菜刀
3. 把胡蘿卜放到切菜板上
4. 提起菜刀
5. 向前推進胡蘿卜
6. 切胡蘿卜
7. 如果切完整個胡蘿卜,就退出,要不然回到第四步繼續執行
從第四步到第七步形成一個循環。重復執行循環內的動作直到滿足條件“切完整個胡蘿卜”。
### while
bash 能夠表達相似的想法。比方說我們想要按照順序從1到5顯示五個數字。可如下構造一個 bash 腳本:
~~~
#!/bin/bash
# while-count: display a series of numbers
count=1
while [ $count -le 5 ]; do
echo $count
count=$((count + 1))
done
echo "Finished."
~~~
當執行的時候,這個腳本顯示如下信息:
~~~
[me@linuxbox ~]$ while-count
1
2
3
4
5
Finished.
~~~
while 命令的語法是:
~~~
while commands; do commands; done
~~~
和 if 一樣, while 計算一系列命令的退出狀態。只要退出狀態為零,它就執行循環內的命令。 在上面的腳本中,創建了變量 count ,并初始化為1。 while 命令將會計算 test 命令的退出狀態。 只要 test 命令返回退出狀態零,循環內的所有命令就會執行。每次循環結束之后,會重復執行 test 命令。 第六次循環之后, count 的數值增加到6, test 命令不再返回退出狀態零,且循環終止。 程序繼續執行循環之后的語句。
我們可以使用一個 while 循環,來提高前面章節的 read-menu 程序:
~~~
#!/bin/bash
# while-menu: a menu driven system information program
DELAY=3 # Number of seconds to display results
while [[ $REPLY != 0 ]]; do
clear
cat <<- _EOF_
Please Select:
1\. Display System Information
2\. Display Disk Space
3\. Display Home Space Utilization
0\. Quit
_EOF_
read -p "Enter selection [0-3] > "
if [[ $REPLY =~ ^[0-3]$ ]]; then
if [[ $REPLY == 1 ]]; then
echo "Hostname: $HOSTNAME"
uptime
sleep $DELAY
fi
if [[ $REPLY == 2 ]]; then
df -h
sleep $DELAY
fi
if [[ $REPLY == 3 ]]; then
if [[ $(id -u) -eq 0 ]]; then
echo "Home Space Utilization (All Users)"
du -sh /home/*
else
echo "Home Space Utilization ($USER)"
du -sh $HOME
fi
sleep $DELAY
fi
else
echo "Invalid entry."
sleep $DELAY
fi
done
echo "Program terminated."
~~~
通過把菜單包含在 while 循環中,每次用戶選擇之后,我們能夠讓程序重復顯示菜單。只要 REPLY 不 等于”0”,循環就會繼續,菜單就能顯示,從而用戶有機會重新選擇。每次動作完成之后,會執行一個 sleep 命令,所以在清空屏幕和重新顯示菜單之前,程序將會停頓幾秒鐘,為的是能夠看到選項輸出結果。 一旦 REPLY 等于“0”,則表示選擇了“退出”選項,循環就會終止,程序繼續執行 done 語句之后的代碼。
## 跳出循環
bash 提供了兩個內部命令,它們可以用來在循環內部控制程序流程。這個 break 命令立即終止一個循環, 且程序繼續執行循環之后的語句。這個 continue 命令導致程序跳過循環中剩余的語句,且程序繼續執行 下一次循環。這里我們看看采用了 break 和 continue 兩個命令的 while-menu 程序版本:
~~~
#!/bin/bash
# while-menu2: a menu driven system information program
DELAY=3 # Number of seconds to display results
while true; do
clear
cat <<- _EOF_
Please Select:
1\. Display System Information
2\. Display Disk Space
3\. Display Home Space Utilization
0\. Quit
_EOF_
read -p "Enter selection [0-3] > "
if [[ $REPLY =~ ^[0-3]$ ]]; then
if [[ $REPLY == 1 ]]; then
echo "Hostname: $HOSTNAME"
uptime
sleep $DELAY
continue
fi
if [[ $REPLY == 2 ]]; then
df -h
sleep $DELAY
continue
fi
if [[ $REPLY == 3 ]]; then
if [[ $(id -u) -eq 0 ]]; then
echo "Home Space Utilization (All Users)"
du -sh /home/*
else
echo "Home Space Utilization ($USER)"
du -sh $HOME
fi
sleep $DELAY
continue
fi
if [[ $REPLY == 0 ]]; then
break
fi
else
echo "Invalid entry."
sleep $DELAY
fi
done
echo "Program terminated."
~~~
在這個腳本版本中,我們設置了一個無限循環(就是自己永遠不會終止的循環),通過使用 true 命令 為 while 提供一個退出狀態。因為 true 的退出狀態總是為零,所以循環永遠不會終止。這是一個 令人驚訝的通用腳本編程技巧。因為循環自己永遠不會結束,所以由程序員在恰當的時候提供某種方法來跳出循環。 此腳本,當選擇”0”選項的時候,break 命令被用來退出循環。continue 命令被包含在其它選擇動作的末尾, 為的是更加高效執行。通過使用 continue 命令,當一個選項確定后,程序會跳過不需要的代碼。例如, 如果選擇了選項”1”,則沒有理由去測試其它選項。
### until
這個 until 命令與 while 非常相似,除了當遇到一個非零退出狀態的時候, while 退出循環, 而 until 不退出。一個 until 循環會繼續執行直到它接受了一個退出狀態零。在我們的 while-count 腳本中, 我們繼續執行循環直到 count 變量的數值小于或等于5。我們可以得到相同的結果,通過在腳本中使用 until 命令:
~~~
#!/bin/bash
# until-count: display a series of numbers
count=1
until [ $count -gt 5 ]; do
echo $count
count=$((count + 1))
done
echo "Finished."
~~~
通過把 test 表達式更改為 $count -gt 5 , until 會在正確的時間終止循環。決定使用 while 循環 還是 until 循環,通常是選擇一個 test 可以編寫地很清楚的循環。
## 使用循環讀取文件
while 和 until 能夠處理標準輸入。這就可以使用 while 和 until 處理文件。在下面的例子中, 我們將顯示在前面章節中使用的 distros.txt 文件的內容:
~~~
#!/bin/bash
# while-read: read lines from a file
while read distro version release; do
printf "Distro: %s\tVersion: %s\tReleased: %s\n" \
$distro \
$version \
$release
done < distros.txt
~~~
為了重定向文件到循環中,我們把重定向操作符放置到 done 語句之后。循環將使用 read 從重定向文件中讀取 字段。這個 read 命令讀取每個文本行之后,將會退出,其退出狀態為零,直到到達文件末尾。到時候,它的 退出狀態為非零數值,因此終止循環。也有可能把標準輸入管道到循環中。
~~~
#!/bin/bash
# while-read2: read lines from a file
sort -k 1,1 -k 2n distros.txt | while read distro version release; do
printf "Distro: %s\tVersion: %s\tReleased: %s\n" \
$distro \
$version \
$release
done
~~~
這里我們接受 sort 命令的標準輸出,然后顯示文本流。然而,因為管道將會在子 shell 中執行 循環,當循環終止的時候,循環中創建的任意變量或賦值的變量都會消失,記住這一點很重要。
## 總結
通過引入循環,和我們之前遇到的分支,子例程和序列,我們已經介紹了程序流程控制的主要類型。 bash 還有一些錦囊妙計,但它們都是關于這些基本概念的完善。
## 拓展閱讀
* Linux 文檔工程中的 Bash 初學者指南一書中介紹了更多的 while 循環實例:
[http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_02.html](http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_02.html)
* Wikipedia 中有一篇關于循環的文章,其是一篇比較長的關于流程控制的文章中的一部分:
[http://en.wikipedia.org/wiki/Control_flow#Loops](http://en.wikipedia.org/wiki/Control_flow#Loops)
- 第一章:引言
- 第二章:什么是shell
- 第三章:文件系統中跳轉
- 第四章:研究操作系統
- 第五章:操作文件和目錄
- 第六章:使用命令
- 第七章:重定向
- 第八章:從shell眼中看世界
- 第九章:鍵盤高級操作技巧
- 第十章:權限
- 第十一章:進程
- 第十二章:shell環境
- 第十三章:VI簡介
- 第十四章:自定制shell提示符
- 第十五章:軟件包管理
- 第十六章:存儲媒介
- 第十七章:網絡系統
- 第十八章:查找文件
- 第十九章:歸檔和備份
- 第二十章:正則表達式
- 第二十一章:文本處理
- 第二十二章:格式化輸出
- 第二十三章:打印
- 第二十四章:編譯程序
- 第二十五章:編寫第一個shell腳本
- 第二十六章:啟動一個項目
- 第二十七章:自頂向下設計
- 第二十八章:流程控制 if分支結構
- 第二十九章:讀取鍵盤輸入
- 第三十章:流程控制 while/until 循環
- 第三十一章:疑難排解
- 第三十二章:流程控制 case分支
- 第三十三章:位置參數
- 第三十四章:流程控制 for循環
- 第三十五章:字符串和數字
- 第三十六章:數組
- 第三十七章:奇珍異寶