shell測試和比較函式--test、[、[[、((、和 if-then-else解密

season0891發表於2010-12-14
shell測試和比較函式--test、[、[[、((、和 if-then-else解密
時間:2009-01-03   作者:佚名   出處:網際網路

您是否為 Bash shell 中大量的測試和比較選項而困惑呢?這個技巧可以幫助您解密不同型別的檔案、算術和字串測試,這樣您就能夠知道什麼時候使用 test、 [ ]、 [[ ]]、 (( )) 或 if-then-else 了。
Bash shell 在當今的許多 Linux® 和 UNIX® 系統上都可使用,是 Linux 上常見的預設 shell。Bash 包含強大的程式設計功能,其中包括豐富的可測試檔案型別和屬性的函式,以及在多數程式語言中可以使用的算術和字串比較函式。理解不同的測試並認識到 shell 還能把一些運算子解釋成 shell 元字元,是成為高階 shell 使用者的重要一步。這篇文章摘自 developerWorks 教程 LPI 102 考試準備,主題 109: Shell、指令碼、程式設計和編譯,介紹瞭如何理解和使用 Bash shell 的測試和比較操作。

這個技巧解釋了 shell 測試和比較函式,演示瞭如何向 shell 新增程式設計功能。您可能已經看到過使用 && 和 || 運算子的簡單 shell 邏輯,它允許您根據前一條命令的退出狀態(正確退出或伴隨錯誤退出)而執行後一條命令。在這個技巧中,將看到如何把這些基本的技術擴充套件成更復雜的 shell 程式設計。

測試

在任何一種程式語言中,學習瞭如何給變數分配值和傳遞引數之後,都需要測試這些值和引數。在 shell 中,測試會設定返回的狀態,這與其他命令執行的功能相同。實際上,test 是個內建命令!

test 和 [

內建命令 test 根據表示式expr 求值的結果返回 0(真)或 1(假)。也可以使用方括號:test  expr 和 [ expr ] 是等價的。 可以用 $? 檢查返回值;可以使用 && 和 || 操作返回值;也可以用本技巧後面介紹的各種條件結構測試返回值。

清單 1. 一些簡單測試

               
[ian@pinguino ~]$ test 3 -gt 4 && echo True || echo false
false
[ian@pinguino ~]$ [ "abc" != "def" ];echo $?
0
[ian@pinguino ~]$ test -d "$HOME" ;echo $?
0


在清單 1 的第一個示例中,-gt 運算子對兩個字元值之間執行算術比較。在第二個示例中,用 [ ] 的形式比較兩個字串不相等。在最後一個示例中,測試 HOME 變數的值,用單目運算子 -d 檢查它是不是目錄。

可以用 -eq、 -ne、-lt、 -le、 -gt 或 -ge 比較算術值,它們分別表示等於、不等於、小於、小於等於、大於、大於等於。

可以分別用運算子 =、 !=、< 和 > 比較字串是否相等、不相等或者第一個字串的排序在第二個字串的前面或後面。單目運算子 -z 測試 null 字串,如果字串非空 -n 返回 True(或者根本沒有運算子)。

說明:shell 也用 < 和 > 運算子進行重定向,所以必須用 \< 或 \> 加以轉義。清單 2 顯示了字串測試的更多示例。檢查它們是否如您預期的一樣。

清單 2. 一些字串測試

               
[ian@pinguino ~]$ test "abc" = "def" ;echo $?
1
[ian@pinguino ~]$ [ "abc" != "def" ];echo $?
0
[ian@pinguino ~]$ [ "abc" \< "def" ];echo $?
0
[ian@pinguino ~]$ [ "abc" \> "def" ];echo $?
1
[ian@pinguino ~]$ [ "abc" \ 1
[ian@pinguino ~]$ [ "abc" \> "abc" ];echo $?
1


表 1 顯示了一些更常見的檔案測試。如果被測試的檔案存在,而且有指定的特徵,則結果為 True。
表 1. 一些常見的檔案測試 運算子     特徵
-d     目錄
-e     存在(也可以用 -a)
-f     普通檔案
-h     符號連線(也可以用 -L)
-p     命名管道
-r     可讀
-s     非空
-S     套接字
-w     可寫
-N     從上次讀取之後已經做過修改

除了上面的單目測試,還可以使用表 2 所示的雙目運算子比較兩個檔案:
表 2. 測試一對檔案 運算子     為 True 的情況
-nt     測試 file1 是否比 file2 更新。修改日期將用於這次和下次比較。
-ot     測試 file1 是否比 file2 舊。
-ef     測試 file1 是不是 file2 的硬連結。

其他一些測試可以用來測試檔案許可之類的內容。請參閱 bash 手冊獲得更多細節或使用 help test 檢視內建測試的簡要資訊。也可以用 help 命令瞭解其他內建命令。

-o 運算子允許測試利用 set -o  選項 設定的各種 shell 選項,如果設定了該選項,則返回 True (0),否則返回 False (1),如清單 3 所示。

清單 3. 測試 shell 選項

               
[ian@pinguino ~]$ set +o nounset
[ian@pinguino ~]$ [ -o nounset ];echo $?
1
[ian@pinguino ~]$ set -u
[ian@pinguino ~]$ test  -o nounset; echo $?
0


最後,-a 和 -o 選項允許使用邏輯運算子 AND 和 OR 將表示式組合在一起。單目運算子 ! 可以使測試的意義相反。可以用括號把表示式分組,覆蓋預設的優先順序。請記住 shell 通常要在子 shell 中執行括號中的表示式,所以需要用 \( 和 \) 轉義括號,或者把這些運算子括在單引號或雙引號內。清單 4 演示了摩根法則在表示式上的應用。

清單 4. 組合和分組測試

               
[ian@pinguino ~]$ test "a" != "$HOME" -a 3 -ge 4 ; echo $?
1
[ian@pinguino ~]$ [ ! \( "a" = "$HOME" -o 3 -lt 4 \) ]; echo $?
1
[ian@pinguino ~]$ [ ! \( "a" = "$HOME" -o '(' 3 -lt 4 ')' ")" ]; echo $?
1



(( 和 [[

test 命令非常強大,但是很難滿足其轉義需求以及字串和算術比較之間的區別。幸運的是,bash 提供了其他兩種測試方式,這兩種方式對熟悉 C、C++ 或 Java® 語法的人來說會更自然些。

(( )) 複合命令 計算算術表示式,如果表示式求值為 0,則設定退出狀態為 1;如果求值為非 0 值,則設定為 0。不需要對 (( 和 )) 之間的運算子轉義。算術只對整數進行。除 0 會產生錯誤,但不會產生溢位。可以執行 C 語言中常見的算術、邏輯和位操作。 let 命令也能執行一個或多個算術表示式。它通常用來為算術變數分配值。

清單 5. 分配和測試算術表示式

               
[ian@pinguino ~]$ let x=2 y=2**3 z=y*3;echo $? $x $y $z
0 2 8 24
[ian@pinguino ~]$ (( w=(y/x) + ( (~ ++x) & 0x0f ) )); echo $? $x $y $w
0 3 8 16
[ian@pinguino ~]$ (( w=(y/x) + ( (~ ++x) & 0x0f ) )); echo $? $x $y $w
0 4 8 13


同使用 (( )) 一樣,利用複合命令 [[ ]] 可以對檔名和字串使用更自然的語法。可以用括號和邏輯運算子把 test 命令支援的測試組合起來。

清單 6. 使用 [[ 複合命令

               
[ian@pinguino ~]$ [[ ( -d "$HOME" ) && ( -w "$HOME" ) ]] && 
>  echo "home is a writable directory"
home is a writable directory


在使用 = 或 != 運算子時,複合命令 [[ 還能在字串上進行模式匹配。匹配的方式就像清單 7 所示的萬用字元匹配。

清單 7. 用 [[ 進行萬用字元測試

               
[ian@pinguino ~]$ [[ "abc def .d,x--" == a[abc]*\ ?d* ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def c" == a[abc]*\ ?d* ]]; echo $?
1
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* ]]; echo $?
1


甚至還可以在 [[ 複合命令內執行算術測試,但是千萬要小心。除非在 (( 複合命令內,否則 < 和 > 運算子會把運算元當成字串比較並在當前排序序列中測試它們的順序。清單 8 用一些示例演示了這一點。

清單 8. 用 [[ 包含算術測試

               
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || (( 3 > 2 )) ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || 3 -gt 2 ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || 3 > 2 ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || a > 2 ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || a -gt 2 ]]; echo $?
-bash: a: unbound variable




條件測試

雖然使用以上的測試和 &&、 || 控制運算子能實現許多程式設計,但 bash 還包含了更熟悉的 “if, then, else” 和 case 結構。學習完這些之後,將學習迴圈結構,這樣您的工具箱將真正得到擴充套件。

If、then、else 語句

bash 的 if 命令是個複合命令,它測試一個測試或命令($?)的返回值,並根據返回值為 True(0)或 False(不為 0)進行分支。雖然上面的測試只返回 0 或 1 值,但命令可能返回其他值。請參閱 LPI 102 考試準備,主題 109: Shell、指令碼、程式設計和編譯 教程學習這方面的更多內容。

Bash 中的 if 命令有一個 then 子句,子句中包含測試或命令返回 0 時要執行的命令列表,可以有一個或多個可選的 elif 子句,每個子句可執行附加的測試和一個 then 子句,子句中又帶有相關的命令列表,最後是可選的 else 子句及命令列表,在前面的測試或 elif 子句中的所有測試都不為真的時候執行,最後使用 fi 標記表示該結構結束。

使用迄今為止學到的東西,現在能夠構建簡單的計算器來計算算術表示式,如清單 9 所示:

清單 9. 用 if、then、else 計算表示式

               
[ian@pinguino ~]$ function mycalc ()
> {
>   local x
>   if [ $# -lt 1 ]; then
>     echo "This function evaluates arithmetic for you if you give it some"
>   elif (( $* )); then
>     let x="$*"
>     echo "$* = $x"
>   else
>     echo "$* = 0 or is not an arithmetic expression"
>   fi
> }
[ian@pinguino ~]$ mycalc 3 + 4
3 + 4 = 7
[ian@pinguino ~]$ mycalc 3 + 4**3
3 + 4**3 = 67
[ian@pinguino ~]$ mycalc 3 + (4**3 /2)
-bash: syntax error near unexpected token `('
[ian@pinguino ~]$ mycalc 3 + "(4**3 /2)"
3 + (4**3 /2) = 35
[ian@pinguino ~]$ mycalc xyz
xyz = 0 or is not an arithmetic expression
[ian@pinguino ~]$ mycalc xyz + 3 + "(4**3 /2)" + abc
xyz + 3 + (4**3 /2) + abc = 35
       


這個計算器利用 local 語句將 x 宣告為區域性變數,只能在 mycalc 函式的範圍內使用。let 函式具有幾個可用的選項,可以執行與它密切關聯的 declare 函式。請參考 bash 手冊或使用 help let 獲得更多資訊。

如清單 9 所示,需要確保在表示式使用 shell 元字元 —— 例如(、)、*、> 和 < 時 —— 正確地對錶達式轉義。無論如何,現在有了一個非常方便的小計算器,可以像 shell 那樣進行算術計算。

在清單 9 中可能注意到 else 子句和最後的兩個示例。可以看到,把 xyz 傳遞給 mycalc 並沒有錯誤,但計算結果為 0。這個函式還不夠靈巧,不能區分最後使用的示例中的字元值,所以不能警告使用者。可以使用字串模式匹配測試(例如
[[ ! ("$*" == *[a-zA-Z]* ]]
,或使用適合自己範圍的形式)消除包含字母表字元的表示式,但是這會妨礙在輸入中使用 16 進位制標記,因為使用 16 進位制標記時可能要用 0x0f 表示 15。實際上,shell 允許的基數最高為 64(使用 base#value 標記),所以可以在輸入中加入 _ 和 @ 合法地使用任何字母表字元。8 進位制和 16 進位制使用常用的標記方式,開頭為 0 表示八進位制,開頭為 0x 或 0X 表示 16 進位制。清單 10 顯示了一些示例。

清單 10. 用不同的基數進行計算

               
[ian@pinguino ~]$ mycalc 015
015 = 13
[ian@pinguino ~]$ mycalc 0xff
0xff = 255
[ian@pinguino ~]$ mycalc 29#37
29#37 = 94
[ian@pinguino ~]$ mycalc 64#1az
64#1az = 4771
[ian@pinguino ~]$ mycalc 64#1azA
64#1azA = 305380
[ian@pinguino ~]$ mycalc 64#1azA_@
64#1azA_@ = 1250840574
[ian@pinguino ~]$ mycalc 64#1az*64**3 + 64#A_@
64#1az*64**3 + 64#A_@ = 1250840574


對輸入進行的額外處理超出了本技巧的範圍,所以請小心使用這個計算器。

elif 語句非常方便。它允許簡化縮排,從而有助於指令碼編寫。在清單 11 中可能會對 type 命令在 mycalc 函式中的輸出感到驚訝。

清單 11. Type mycalc

               
[ian@pinguino ~]$ type mycalc
mycalc is a function
mycalc ()
{
    local x;
    if [ $# -lt 1 ]; then
        echo "This function evaluates arithmetic for you if you give it some";
    else
        if (( $* )); then
            let x="$*";
            echo "$* = $x";
        else
            echo "$* = 0 or is not an arithmetic expression";
        fi;
    fi
}
 


當然,也可以只用 $((  表示式  )) 和 echo 命令進行 shell 算術運算,如清單 12 所示。這樣就不必學習關於函式或測試的任何內容,但是請注意 shell 不會解釋元字元,例如 *,因此元字元不能在 ((  表示式  )) 或 [[  表示式  ]] 中那樣正常發揮作用。

清單 12. 在 shell 中用 echo 和 $(( )) 直接進行計算

               
[ian@pinguino ~]$  echo $((3 + (4**3 /2)))
35

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/90618/viewspace-681782/,如需轉載,請註明出處,否則將追究法律責任。

相關文章