在前面的章節(jié)中,我們開發(fā)了菜單驅(qū)動程序,來產(chǎn)生各種各樣的系統(tǒng)信息。雖然程序能夠運行, 但它仍然存在重大的可用問題。它只能執(zhí)行單一的選擇,然后終止。更糟糕地是,如果做了一個 無效的選擇,程序會以錯誤終止,而沒有給用戶提供再試一次的機會。如果我們能構(gòu)建程序, 以致于程序能夠重復顯示菜單,而且能一次由一次的選擇,直到用戶選擇退出程序,這樣的程序會更好一些。
在這一章中,我們將看一個叫做循環(huán)的程序概念,其可用來使程序的某些部分重復。shell 為循環(huán)提供了三個復合命令。 本章我們將查看其中的兩個命令,隨后章節(jié)介紹第三個命令。
日常生活中充滿了重復性的活動。每天去散步,遛狗,切胡蘿卜,所有任務都要重復一系列的步驟。 讓我們以切胡蘿卜為例。如果我們用偽碼表達這種活動,它可能看起來像這樣:
準備切菜板
準備菜刀
把胡蘿卜放到切菜板上
提起菜刀
向前推進胡蘿卜
切胡蘿卜
從第四步到第七步形成一個循環(huán)。重復執(zhí)行循環(huán)內(nèi)的動作直到滿足條件“切完整個胡蘿卜”。
bash 能夠表達相似的想法。比方說我們想要按照順序從1到5顯示五個數(shù)字。可如下構(gòu)造一個 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."
當執(zhí)行的時候,這個腳本顯示如下信息:
[me@linuxbox ~]$ while-count
1
2
3
4
5
Finished.
while 命令的語法是:
while commands; do commands; done
和 if 一樣, while 計算一系列命令的退出狀態(tài)。只要退出狀態(tài)為零,它就執(zhí)行循環(huán)內(nèi)的命令。 在上面的腳本中,創(chuàng)建了變量 count ,并初始化為1。 while 命令將會計算 test 命令的退出狀態(tài)。 只要 test 命令返回退出狀態(tài)零,循環(huán)內(nèi)的所有命令就會執(zhí)行。每次循環(huán)結(jié)束之后,會重復執(zhí)行 test 命令。 第六次循環(huán)之后, count 的數(shù)值增加到6, test 命令不再返回退出狀態(tài)零,且循環(huán)終止。 程序繼續(xù)執(zhí)行循環(huán)之后的語句。
我們可以使用一個 while 循環(huán),來提高前面章節(jié)的 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 循環(huán)中,每次用戶選擇之后,我們能夠讓程序重復顯示菜單。只要 REPLY 不 等于"0",循環(huán)就會繼續(xù),菜單就能顯示,從而用戶有機會重新選擇。每次動作完成之后,會執(zhí)行一個 sleep 命令,所以在清空屏幕和重新顯示菜單之前,程序?qū)nD幾秒鐘,為的是能夠看到選項輸出結(jié)果。 一旦 REPLY 等于“0”,則表示選擇了“退出”選項,循環(huán)就會終止,程序繼續(xù)執(zhí)行 done 語句之后的代碼。
bash 提供了兩個內(nèi)部命令,它們可以用來在循環(huán)內(nèi)部控制程序流程。這個 break 命令立即終止一個循環(huán), 且程序繼續(xù)執(zhí)行循環(huán)之后的語句。這個 continue 命令導致程序跳過循環(huán)中剩余的語句,且程序繼續(xù)執(zhí)行 下一次循環(huán)。這里我們看看采用了 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."
在這個腳本版本中,我們設置了一個無限循環(huán)(就是自己永遠不會終止的循環(huán)),通過使用 true 命令 為 while 提供一個退出狀態(tài)。因為 true 的退出狀態(tài)總是為零,所以循環(huán)永遠不會終止。這是一個 令人驚訝的通用腳本編程技巧。因為循環(huán)自己永遠不會結(jié)束,所以由程序員在恰當?shù)臅r候提供某種方法來跳出循環(huán)。 此腳本,當選擇"0"選項的時候,break 命令被用來退出循環(huán)。continue 命令被包含在其它選擇動作的末尾, 為的是更加高效執(zhí)行。通過使用 continue 命令,當一個選項確定后,程序會跳過不需要的代碼。例如, 如果選擇了選項"1",則沒有理由去測試其它選項。
這個 until 命令與 while 非常相似,除了當遇到一個非零退出狀態(tài)的時候, while 退出循環(huán), 而 until 不退出。一個 until 循環(huán)會繼續(xù)執(zhí)行直到它接受了一個退出狀態(tài)零。在我們的 while-count 腳本中, 我們繼續(xù)執(zhí)行循環(huán)直到 count 變量的數(shù)值小于或等于5。我們可以得到相同的結(jié)果,通過在腳本中使用 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 會在正確的時間終止循環(huán)。決定使用 while 循環(huán) 還是 until 循環(huán),通常是選擇一個 test 可以編寫地很清楚的循環(huán)。
while 和 until 能夠處理標準輸入。這就可以使用 while 和 until 處理文件。在下面的例子中, 我們將顯示在前面章節(jié)中使用的 distros.txt 文件的內(nèi)容:
#!/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
為了重定向文件到循環(huán)中,我們把重定向操作符放置到 done 語句之后。循環(huán)將使用 read 從重定向文件中讀取 字段。這個 read 命令讀取每個文本行之后,將會退出,其退出狀態(tài)為零,直到到達文件末尾。到時候,它的 退出狀態(tài)為非零數(shù)值,因此終止循環(huán)。也有可能把標準輸入管道到循環(huán)中。
#!/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 中執(zhí)行 循環(huán),當循環(huán)終止的時候,循環(huán)中創(chuàng)建的任意變量或賦值的變量都會消失,記住這一點很重要。
通過引入循環(huán),和我們之前遇到的分支,子例程和序列,我們已經(jīng)介紹了程序流程控制的主要類型。 bash 還有一些錦囊妙計,但它們都是關(guān)于這些基本概念的完善。
Linux 文檔工程中的 Bash 初學者指南一書中介紹了更多的 while 循環(huán)實例:
http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_02.html
Wikipedia 中有一篇關(guān)于循環(huán)的文章,其是一篇比較長的關(guān)于流程控制的文章中的一部分: