每一種條件陳述式的基礎都是判斷什麼是真什麼是假。是否瞭解其工作原理將決定您編寫的是品質一般的指令碼還是您將引以為榮的指令碼。
Shell 指令碼的能力時常被低估,但實際上其能力的發揮受制於指令碼撰寫者的能力。您瞭解得越多,您就越能像變戲法似地撰寫一個檔案來使任務自動化和簡化您的管理工作。
在 shell 指令碼中進行的每一種操作(除最簡單的命令編組之外)都需要檢查條件。所有的 shell 指令碼“邏輯” — 廣義意義下的“邏輯” — 通常都可以分為以下三大類:
if {condition exists} then ...
while {condition exists} do ...
until {condition exists} do ...
無論隨後的操作是什麼,這些基於邏輯的命令都依靠判斷一種條件是否真實存在來決定後續的操作。test 命令是使得在每一種情況下都能夠確定要判斷的條件是否存在的工具 + 生產力。因此,徹底瞭解這個命令對於撰寫成功的 shell 指令碼至關重要。
工作原理
test 命令最短的定義可能是評估一個運算式;如果條件為真,則返回一個 0 值。如果運算式不為真,則返回一個大於 0 的值 — 也可以將其稱為假值。檢查最後所執行命令的狀態的最簡便方法是使用 $? 值。出於示範的目的,本文中的例子全部使用了這個參數。
test 命令期望在命令列中找到一個參數,當 shell 沒有為變數賦值時,則將該變數視為空白。這意味著在處理指令碼時,一旦指令碼尋找的參數不存在,則 test 將報告該錯誤。
當試圖保護指令碼時,您可以通過將所有參數包含在雙引號中來解決這個問題。然後 shell 將變數展開,如果變數沒有值,那麼將傳遞一個空值給
test。另一種方法是在指令碼內增加一個額外檢查過程來判斷是否設定了命令列參數。如果沒有設定命令列參數,那麼指令碼會告訴使用者缺少參數,然後退出。我們
會通過一些例子來更具體地說明所有這些內容。
test 和 [ 命令
雖然 Linux 和 UNIX 的每個版本中都包含 test 命令,但該命令有一個更常用的別名 — 左方括弧:[。test 及其別名通常都可以在 /usr/bin 或 /bin (取決於作業系統版本和供應商)中找到。
當您使用左方括弧而非 test 時,其後必須始終跟著一個空格、要評估的條件、一個空格和右方括弧。右方括弧不是任何東西的別名,而是表示所需評估參數的結束。條件兩邊的空格是必需的,這表示要調用 test,以區別於同樣經常使用方括弧的字元/模式比對操作。
test 和 [ 的文法如下:
test expression
[ expression ]
在這兩種情況下,test 都評估一個運算式,然後返回真或假。如果它和 if、while 或 until 命令結合使用,則您可以對程式流進行廣泛的控制。不過,您無需將 test 命令與任何其它結構一起使用;您可以從命令列直接運行它來檢查幾乎任何東西的狀態。
因為它們彼此互為別名,所以使用 test 或 [ 均需要一個運算式。運算式一般是文本、數字或檔案和目錄屬性的比較,並且可以包含變數、常量和運算子。運算子可以是字串運算子、整數運算子、檔案運算子或布林運算子 — 我們將在以下各部分依次介紹每一種運算子。
test 檔案運算子
利用這些運算子,您可以在程式中根據對檔案類型的評估結果執行不同的操作:
-b file 如果檔案為一個塊特殊檔案,則為真
-c file 如果檔案為一個字元特殊檔案,則為真
-d file 如果檔案為一個目錄,則為真
-e file 如果檔案存在,則為真
-f file 如果檔案為一個普通檔案,則為真
-g file 如果設定了檔案的 SGID 位,則為真
-G file 如果檔案存在且歸該組所有,則為真
-k file 如果設定了檔案的粘著位,則為真
-O file 如果檔案存在並且歸該使用者所有,則為真
-p file 如果檔案為一個具名管道,則為真
-r file 如果檔案可讀,則為真
-s file 如果檔案的長度不為零,則為真
-S file 如果檔案為一個通訊端特殊檔案,則為真
-t fd 如果 fd 是一個與終端相連的開啟的檔案描述符(fd 預設為 1),則為真
-u file 如果設定了檔案的 SUID 位,則為真
-w file 如果檔案可寫,則為真
-x file 如果檔案可執行,則為真
以下樣本顯示了此簡單操作的運行情況:
$ ls -l
total 33
drwxr-xr-w 2 root root 1024 Dec 5 05:05 LST
-rw-rw-rw- 1 emmett users 27360 Feb 6 07:30 evan
-rwsrwsrwx 1 root root 152 Feb 6 07:32 hannah
drwxr-xr-x 2 emmett users 1024 Feb 6 07:31 karen
-rw------- 1 emmett users 152 Feb 6 07:29 kristin
-rw-r--r-- 1 emmett users 152 Feb 6 07:29 spencer
$
$ test -r evan
$ echo $?
0
$ test -r walter
$ echo $?
1
$
由於第一次評估為真 — 檔案存在且可讀 — 傳回值為真,或 0。由於第二次評估的檔案不存在,該值為假,傳回值不為零。將值指定為零或非零很重要,因為在失敗時不會始終返回 1(雖然這是通常返回的值),可能返回一個非零值。
正如開頭所提到的,除了使用 test 外,您還可以用方括弧 [ ] 將命令括住來向 shell 發出同樣的命令 — 如下所示:
$ [ -w evan ]
$ echo $?
0
$ [ -x evan ]
$ echo $?
1
$
同樣,第一個運算式為真,第二個運算式為假 — 正如傳回值所指示的那樣。您還可以使用以下命令將兩個檔案彼此進行比較:
file1 -ef file2 測試以判斷兩個檔案是否與同一個裝置相連,是否擁有相同的 inode 編號
file1 -nt file2 測試以判斷第一個檔案是否比第二個檔案更新(由修改日期決定)
file1 -ot file2 測試以判斷第一個檔案是否比第二個檔案更舊
以下樣本顯示了使用這些運算子比較檔案的結果:
$ [ evan -nt spencer ]
$ echo $?
0
$ [ karen -ot spencer ]
$ echo $?
1
$
名為 evan 的檔案比名為 spencer 的檔案更新,因而評估為真。類似地,名為 karen 的檔案比名為 spencer 的檔案更新,因此該評估為假。
字串比較運算子
如標題所示,這組函數比較字串的值。您可以檢查它們是否存在、是否相同或者是否不同。
String 測試以判斷字串是否不為空白
-n string 測試以判斷字串是否不為空白;字串必須為 test 所識別
-z string 測試以判斷字串是否為空白;字串必須為 test 所識別
string1 = string2 測試以判斷 string1 是否與 string2 相同
string1 != string2 測試以判斷 string1 是否與 string2 不同
對任何變數進行的最有用的測試之一是判斷它的值是否不為空白,可以簡單地將其放在 test 命令列中執行這種測試,如下例所示:
$ test "$variable"
強烈建議進行此種測試時用雙引號將變數括住,以讓 shell 識別變數(即使變數為空白)。預設情況下執行的基底字元串評估和 -n 測試從功能上講是相同的,如以下樣本所示:
#example1
if test -n "$1"
then
echo "$1"
fi
執行以上例子中的代碼將根據 $1 是否存在給出以下結果:
$ example1 friday
friday
$
$ example1
$
如果將代碼更改為以下形式,則結果將相同:
#example2
if test "$1"
then
echo "$1"
fi
如下所示:
$ example2 friday
friday
$
$ example2
$
所有這些表明,通常不需要 -n,它代表預設操作。
要從一個不同的角度來查看各種可能性,您可以用另一個選項來替換 -n,並檢查該值是否為空白(相對於非空)。這可以用 -z 選項來實現,代碼為:
#example3
if test -z "$1"
then
echo "no values were specified"
fi
運行如下:
$ example3
no values were specified
$ example3 friday
$
如果在沒有命令列參數的情況下運行該程式,而運算式評估為真,那麼將執行程式塊中的文本。如果在命令列中有值,則指令碼退出,不執行任何操作。將評估操作放在指令碼的開頭非常有用,這可以在可能產生錯誤的進一步處理之前預先檢查變數值。
其餘的字串運算子對兩個變數/字串之間的精確匹配或其中的差異(您也可以稱之為等價性和“不等價性”)進行評估。第一個例子對匹配進行測試:
$ env
LOGNAME=emmett
PAGER=less
SHELL=/bin/bash
TERM=linux
$
$ [ "$LOGNAME" = "emmett" ]
$ echo $?
0
$
$ [ "$LOGNAME" = "kristin" ]
$ echo $?
1
$
或者,該評估可以以指令碼的形式用於決定是否運行指令碼:
#example4
if [ "$LOGNAME" = "emmett" ]
then
echo "processing beginning"
else
echo "incorrect user"
fi
這種方法可以用來尋找任意的值(如終端類型或 shell 類型),在允許指令碼運行之前這些值必須匹配。請注意,= 或 !=
運算子的優先順序高於其它大多數可指定選項,且要求必須伴有運算式。因此,除了比較字串的選項之外,= 或 !=
都不能和檢查某種東西(如可讀檔案、可執行檔或目錄)的存在性的選項一起使用。
整數比較子
正如字串比較運算子驗證字串相等或不同一樣,整數比較子對數字執行相同的功能。如果變數的值匹配則運算式測試為真,如果不匹配,則為假。整數比較子不處理字串(正如字串運算子不處理數字一樣):
int1 -eq int2 如果 int1 等於 int2,則為真
int1 -ge int2 如果 int1 大於或等於 int2,則為真
int1 -gt int2 如果 int1 大於 int2,則為真
int1 -le int2 如果 int1 小於或等於 int2,則為真
int1 -lt int2 如果 int1 小於 int2,則為真
int1 -ne int2 如果 int1 不等於 int2,則為真
以下樣本顯示了一個程式碼片段,其中在命令列中給出的值必須等於 7:
#example5
if [ $1 -eq 7 ]
then
echo "You've entered the magic number."
else
echo "You've entered the wrong number."
fi
運行中:
$ example5 6
You've entered the wrong number.
$
$ example5 7
You've entered the magic number.
$
和字串一樣,比較的值可以是在指令碼外為變數賦的值,而不必總是在命令列中提供。以下樣本示範了實現這一點的一種方法:
#example6
if [ $1 -gt $number ]
then
echo "Sorry, but $1 is too high."
else
echo "$1 will work."
fi
$ set number=7
$ export number
$ example6 8
Sorry, but 8 is too high.
$ example6 7
7 will work.
$
整數比較子最佳的用途之一是評估指定的命令列變數的數目,並判斷它是否符合所要求的標準。例如,如果某個特定的命令只能在有三個或更少變數的情況下運行,
#example7 - display variables, up to three
if [ "$#" -gt 3 ]
then
echo "You have given too many variables."
exit $#
fi
只要指定三個或更少的變數,該樣本指令碼將正常運行(並傳回值 0)。如果指定了三個以上的變數,則將顯示錯誤訊息,且常式將退出 — 同時返回與命令列中給定的變數數相等的結束代碼。
對這個過程進行修改可以用來在允許運行報表之前判斷當天是否是本月的最後幾天:
#example8 - to see if it is near the end of the month#
set `date` # use backward quotes
if [ "$3" -ge 21 ]
then
echo "It is close enough to the end of the month to proceed"
else
echo "This report cannot be run until after the 21st of the month"
exit $3
fi
在這個例子中,設定了六個變數(通過空格彼此分開):
$1 = Fri
$2 = Feb
$3 = 6
$4 = 08:56:30
$5 = EST
$6 = 2004
這些值可以在指令碼中使用,就像它們是在命令列中輸入的一樣。請注意,退出命令再次返回一個值 — 在這種情況下,返回的值是從 $3 的值中得到的日期。這一技巧在故障診斷時會非常有用 — 如果您認為指令碼應該運行而沒有運行,那麼請查看 $? 的值。
一種類似的想法可能是撰寫一個只在每個月的第三個星期三啟動並執行指令碼。第三個星期三一定在該月的 15 日到 21 日之間。使用
cron,您可以呼叫指令碼在 15 日到 21 日之間每天的一個指定時間運行,然後使用指令碼的第一行檢查 $1(在設定日期之後)的值是否為
Thu。如果為 Thu,那麼執行剩下的指令碼,如果不是,則退出。
而另一個想法可能是,只允許指令碼在超過 6:00 p.m. (18:00),所有使用者都回家之後運行。只要撰寫指令碼,使其在值低於 18 時退出,並通過使用以下命令來擷取時間(將其設為 $1)
set `date +%H`
布林運算子
布林運算子在幾乎每種語言中的工作方式都相同 — 包括 shell 指令碼。在 nutshell 中,它們檢查多個條件為真或為假,或者針對假的條件而不是真的條件採取操作。與 test 搭配使用的運算子有
! expr 如果運算式評估為假,則為真
expr1 -a expr2 如果 expr1 和 expr2 評估為真,則為真
expr1 -o expr2 如果 expr1 或 expr2 評估為真,則為真
可以用 != 運算子代替 = 進行字串評估。這是最簡單的布林運算子之一,對 test 的正常結果取非。
其餘兩個運算子中的第一個是 -a(即 AND)運算子。要使測試最終為真,兩個運算式都必須評估為真。如果任何一個評估為假,則整個測試將評估為假。例如,
$ env
HOME=/
LOGNAME=emmett
MAIL=/usr/mail/emmett
PATH=:/bin:/usr/bin:/usr/lbin
TERM=linux
TZ=EST5:0EDT
$
$ [ "$LOGNAME" = "emmett" -a "$TERM" = "linux" ]
$ echo $?
0
$
$ [ "LOGNAME" = "karen" -a "$TERM" = "linux" ]
$ echo $?
1
$
在第一個評估中,兩個條件都測試為真(在一個 linux 終端上登入的是 emmett),因此整個評估為真。在第二個評估中,終端檢查正確但使用者不正確,因此整個評估為假。
簡而言之,AND 運算子可以確保代碼只在兩個條件都滿足時才執行。相反,只要任何一個運算式測試為真,OR (-o) 運算子即為真。我們來修改先前的例子,並將其放到一個指令碼中來說明這一點:
#example9
if [ "$LOGNAME" = "emmett" -o "$TERM" = "linux" ]
then
echo "Ready to begin."
else
echo "Incorrect user and terminal."
fi
$ env
HOME=/
LOGNAME=emmett
MAIL=/usr/mail/emmett
PATH=:/bin:/usr/bin:/usr/lbin
TERM=linux
TZ=EST5:0EDT
$ example9
Ready to begin.
$
$ LOGNAME=karen
$ example9
Ready to begin.
$
在指令碼第一次運行時,評估判斷使用者是否等於 emmett。如果發現使用者等於 emmett,則指令碼轉至 echo
語句,並跳過其餘的檢查。它從不檢查終端是否等於
linux,因為它只需要找到一條為真的語句就可以使整個運算為真。在指令碼第二次運行時,它判斷使用者不是 emmett,因此它將檢查並發現終端確實是
linux。由於一個條件為真,指令碼現在轉至 echo 命令。為了引出第二條訊息,兩個條件都必須為假。
在先前確定時間是否為月末的例子中,可以執行類似的檢查來防止使用者試圖在周末運行指令碼:
#example10 - Do not let the script run over the weekend#
set `date` # use backward quotes
if [ "$1" = "Sat" -o "$1" = "Sun" ]
then
echo "This report cannot be run over the weekend."
fi
一些有用的樣本
樣本 1:在指令檔中出現的“邏輯”的最簡單的形式(如本文所有樣本中所示)是“if ...
then”語句。先前的一個程式碼片段檢查是否存在一定數量的變數,然後將這些變數回顯。假設我們對此稍微做一些修改,比如我們想回顯變數,並且每次回顯均減
去最左邊的變數,以顯示一個倒的三角形。
雖然這聽起來很簡單,但實際並非如此;這是您在執行大規模處理時想實現的方式:處理第一個變數、轉移、處理下一個變數……
出於示範的目的,可以按以下方式撰寫指令碼中的重要行:
#example11 - display declining variables, up to three
if [ "$#" -gt 3 ] # see if more than three variables are given
then
echo "You have given more than three variables."
exit
fi
echo $*
if test -n "$2"
then
shift
echo $*
fi
if test -n "$2"
then
shift
echo $*
fi
它將按以下方式執行:
$ example11 one
one
$
$ example11 one two
one two
two
$
$ example11 one two three
one two three
two three
three
$
$ example11 one two three four
You have given more than three variables.
$
出於檢查的目的將數量限制為三個變數的原因是減少在例子中要檢查的行數。一切都按部就班地進行,雖然它令人難以置信地混亂;使用者因使用了超過程式依設計所能處理的變數數而得到警告,且指令碼退出。如果變數數為 3 或更少,則運算的核心部分開始執行。
回顯變數,執行測試以查看另一個變數是否存在。如果另一個變數存在,則執行一次轉移,回顯該變數,執行另一測試,等等。總共使用了 16
個有效行,而程式僅能處理不超過三個變數 —
非常混亂。假設消除變數數的限制,程式可以處理任意數量的變數。經過一些修改,指令碼被縮短(美化)了,並能處理任意數量的變數:
#example12 - display declining variables, any number
while [ "$#" -gt 0 ]
do
echo $*
shift
done
$ example12 1 2 3 4 5 6 7 8 9 0
1 2 3 4 5 6 7 8 9 0
2 3 4 5 6 7 8 9 0
3 4 5 6 7 8 9 0
4 5 6 7 8 9 0
5 6 7 8 9 0
6 7 8 9 0
7 8 9 0
8 9 0
9 0
0
現在減少到只有 5 個有效行,且消除了第一個指令碼三個變數的限制,並在運行時要更高效。
樣本 2:無論何時當在指令碼內執行與處理相關的操作時,下一個操作將始終檢查上一操作的狀態,以確認它已成功完成。您可以通過檢查 $? 的狀態並驗證它等於 0 來實現這一目的。例如,如果一個資料目錄是否能訪問非常重要,
#example13
TEMP=LST
cd $TEMP
if [ $?-ne 0 ]
then
echo "Data directory could not be found."
Exit
fi
處理錯誤
資源
下載針對 Linux 的 Oracle 資料庫 10g
Oracle 資料庫 10g 第 1 版 (10.1.0.2) 現在可用於 Linux x86 和 Linux Itanium 平台;請在此從 OTN 上免費下載。
訪問 Linux 技術中心
收藏本頁,以擷取關於 Linux 系統管理員最佳應用的一般技術資訊,以及關於 Oracle-on-Linux 產品群的具體技術資訊。
相關文章
Linux 相關技術文章的存檔
test 命令常常出現的錯誤事實上只有兩種類型。第一種是未使用正確的評估類型,例如將字串變數與整型變數進行比較或者將帶填充的字串與不帶填充的字串進行比較。仔細評估您使用的變數將使您最終找到錯誤的根源,並讓您能夠解決這些問題。
第二種錯誤類型包括將方括弧誤認為別名之外的某個東西。方括弧與其內容之間必須有一個空格;否則,它們將不能解釋其中的對象。例如,
$ [ "$LOGNAME" -gt 9]
test:] missing
$
請注意,錯誤訊息指示 test 存在問題,即使使用了別名 ]。這些問題很容易發現,因為錯誤訊息準確地將這些問題顯示出來,然後您可以增加必要的空格。
結論
要在 shell 指令碼中構建邏輯,您必須添加條件陳述式。每一條這種語句的核心都是對條件的評估,以判斷它是否存在 — 通過使用 test 命令完成評估。瞭解它和它的別名(左方括弧 ([)的工作原理將使您能夠撰寫可以完成一些複雜操作的 shell 指令碼。