鍍金池/ 教程/ Linux/ 流程控制:while/until 循環(huán)
網(wǎng)絡(luò)系統(tǒng)
打印
重定向
使用命令
位置參數(shù)
權(quán)限
文本處理
疑難排解
layout: book-zh title: 自定制 shell 提示符
查找文件
layout: book-zh title: vi 簡介
shell 環(huán)境
什么是 shell
編譯程序
鍵盤高級操作技巧
流程控制:case 分支
流程控制:if 分支結(jié)構(gòu)
layout: book-zh title: 軟件包管理
進(jìn)程
存儲媒介
格式化輸出
編寫第一個 Shell 腳本
啟動一個項目
流程控制:while/until 循環(huán)
文件系統(tǒng)中跳轉(zhuǎn)
字符串和數(shù)字
讀取鍵盤輸入
歸檔和備份
探究操作系統(tǒng)
流程控制:for 循環(huán)
自頂向下設(shè)計
數(shù)組
操作文件和目錄
奇珍異寶
從 shell 眼中看世界
正則表達(dá)式

流程控制:while/until 循環(huán)

在前面的章節(jié)中,我們開發(fā)了菜單驅(qū)動程序,來產(chǎn)生各種各樣的系統(tǒng)信息。雖然程序能夠運行, 但它仍然存在重大的可用問題。它只能執(zhí)行單一的選擇,然后終止。更糟糕地是,如果做了一個 無效的選擇,程序會以錯誤終止,而沒有給用戶提供再試一次的機(jī)會。如果我們能構(gòu)建程序, 以致于程序能夠重復(fù)顯示菜單,而且能一次由一次的選擇,直到用戶選擇退出程序,這樣的程序會更好一些。

在這一章中,我們將看一個叫做循環(huán)的程序概念,其可用來使程序的某些部分重復(fù)。shell 為循環(huán)提供了三個復(fù)合命令。 本章我們將查看其中的兩個命令,隨后章節(jié)介紹第三個命令。

循環(huán)

日常生活中充滿了重復(fù)性的活動。每天去散步,遛狗,切胡蘿卜,所有任務(wù)都要重復(fù)一系列的步驟。 讓我們以切胡蘿卜為例。如果我們用偽碼表達(dá)這種活動,它可能看起來像這樣:

  1. 準(zhǔn)備切菜板

  2. 準(zhǔn)備菜刀

  3. 把胡蘿卜放到切菜板上

  4. 提起菜刀

  5. 向前推進(jìn)胡蘿卜

  6. 切胡蘿卜

  7. 如果切完整個胡蘿卜,就退出,要不然回到第四步繼續(xù)執(zhí)行

從第四步到第七步形成一個循環(huán)。重復(fù)執(zhí)行循環(huán)內(nèi)的動作直到滿足條件“切完整個胡蘿卜”。

while

bash 能夠表達(dá)相似的想法。比方說我們想要按照順序從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."

當(dāng)執(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é)束之后,會重復(fù)執(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)中,每次用戶選擇之后,我們能夠讓程序重復(fù)顯示菜單。只要 REPLY 不 等于"0",循環(huán)就會繼續(xù),菜單就能顯示,從而用戶有機(jī)會重新選擇。每次動作完成之后,會執(zhí)行一個 sleep 命令,所以在清空屏幕和重新顯示菜單之前,程序?qū)nD幾秒鐘,為的是能夠看到選項輸出結(jié)果。 一旦 REPLY 等于“0”,則表示選擇了“退出”選項,循環(huán)就會終止,程序繼續(xù)執(zhí)行 done 語句之后的代碼。

跳出循環(huán)

bash 提供了兩個內(nèi)部命令,它們可以用來在循環(huán)內(nèi)部控制程序流程。這個 break 命令立即終止一個循環(huán), 且程序繼續(xù)執(zhí)行循環(huán)之后的語句。這個 continue 命令導(dǎo)致程序跳過循環(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."

在這個腳本版本中,我們設(shè)置了一個無限循環(huán)(就是自己永遠(yuǎn)不會終止的循環(huán)),通過使用 true 命令 為 while 提供一個退出狀態(tài)。因為 true 的退出狀態(tài)總是為零,所以循環(huán)永遠(yuǎn)不會終止。這是一個 令人驚訝的通用腳本編程技巧。因為循環(huán)自己永遠(yuǎn)不會結(jié)束,所以由程序員在恰當(dāng)?shù)臅r候提供某種方法來跳出循環(huán)。 此腳本,當(dāng)選擇"0"選項的時候,break 命令被用來退出循環(huán)。continue 命令被包含在其它選擇動作的末尾, 為的是更加高效執(zhí)行。通過使用 continue 命令,當(dāng)一個選項確定后,程序會跳過不需要的代碼。例如, 如果選擇了選項"1",則沒有理由去測試其它選項。

until

這個 until 命令與 while 非常相似,除了當(dāng)遇到一個非零退出狀態(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 表達(dá)式更改為 $count -gt 5 , until 會在正確的時間終止循環(huán)。決定使用 while 循環(huán) 還是 until 循環(huán),通常是選擇一個 test 可以編寫地很清楚的循環(huán)。

使用循環(huán)讀取文件

while 和 until 能夠處理標(biāo)準(zhǔn)輸入。這就可以使用 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)為零,直到到達(dá)文件末尾。到時候,它的 退出狀態(tài)為非零數(shù)值,因此終止循環(huán)。也有可能把標(biāo)準(zhǔ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 命令的標(biāo)準(zhǔn)輸出,然后顯示文本流。然而,因為管道將會在子 shell 中執(zhí)行 循環(huán),當(dāng)循環(huán)終止的時候,循環(huán)中創(chuàng)建的任意變量或賦值的變量都會消失,記住這一點很重要。

總結(jié)

通過引入循環(huán),和我們之前遇到的分支,子例程和序列,我們已經(jīng)介紹了程序流程控制的主要類型。 bash 還有一些錦囊妙計,但它們都是關(guān)于這些基本概念的完善。

拓展閱讀