- 論壇徽章:
- 0
|
利用 test 命令提高您的腳本編制水平
作者:Emmett Dulaney
每一種條件語句的基礎都是判斷什么是真什么是假。是否了解其工作原理將決定您編寫的是質(zhì)量一般的腳本還是您將引以為榮的腳本。
Shell 腳本的能力時常被低估,但實際上其能力的發(fā)揮受制于腳本撰寫者的能力。您了解得越多,您就越能像變戲法似地撰寫一個文件來使任務自動化和簡化您的管理工作。
在 shell 腳本中進行的每一種操作(除最簡單的命令編組之外)都需要檢查條件。所有的 shell 腳本“邏輯” — 廣義意義下的“邏輯” — 通常都可以分為以下三大類:
if {condition exists} then ...
while {condition exists} do ...
until {condition exists} do ...
無論隨后的操作是什么,這些基于邏輯的命令都依靠判斷一種條件是否真實存在來決定后續(xù)的操作。test 命令是使得在每一種情況下都能夠確定要判斷的條件是否存在的實用工具。因此,徹底了解這個命令對于撰寫成功的 shell 腳本至關(guān)重要。
工作原理
test 命令最短的定義可能是評估一個表達式;如果條件為真,則返回一個 0 值。如果表達式不為真,則返回一個大于 0 的值 — 也可以將其稱為假值。檢查最后所執(zhí)行命令的狀態(tài)的最簡便方法是使用 $? 值。出于演示的目的,本文中的例子全部使用了這個參數(shù)。
test 命令期望在命令行中找到一個參數(shù),當 shell 沒有為變量賦值時,則將該變量視為空。這意味著在處理腳本時,一旦腳本尋找的參數(shù)不存在,則 test 將報告該錯誤。
當試圖保護腳本時,您可以通過將所有參數(shù)包含在雙引號中來解決這個問題。然后 shell 將變量展開,如果變量沒有值,那么將傳遞一個空值給 test。另一種方法是在腳本內(nèi)增加一個額外檢查過程來判斷是否設置了命令行參數(shù)。如果沒有設置命令行參數(shù),那么腳本會告訴用戶缺少參數(shù),然后退出。我們會通過一些例子來更具體地說明所有這些內(nèi)容。
test 和 [ 命令
雖然 Linux 和 UNIX 的每個版本中都包含 test 命令,但該命令有一個更常用的別名 — 左方括號:[。test 及其別名通常都可以在 /usr/bin 或 /bin (取決于操作系統(tǒng)版本和供應商)中找到。
當您使用左方括號而非 test 時,其后必須始終跟著一個空格、要評估的條件、一個空格和右方括號。右方括號不是任何東西的別名,而是表示所需評估參數(shù)的結(jié)束。條件兩邊的空格是必需的,這表示要調(diào)用 test,以區(qū)別于同樣經(jīng)常使用方括號的字符/模式匹配操作。
test 和 [ 的語法如下:
test expression
[ expression ]
在這兩種情況下,test 都評估一個表達式,然后返回真或假。如果它和 if、while 或 until 命令結(jié)合使用,則您可以對程序流進行廣泛的控制。不過,您無需將 test 命令與任何其它結(jié)構(gòu)一起使用;您可以從命令行直接運行它來檢查幾乎任何東西的狀態(tài)。
因為它們彼此互為別名,所以使用 test 或 [ 均需要一個表達式。表達式一般是文本、數(shù)字或文件和目錄屬性的比較,并且可以包含變量、常量和運算符。運算符可以是字符串運算符、整數(shù)運算符、文件運算符或布爾運算符 — 我們將在以下各部分依次介紹每一種運算符。
test 文件運算符
利用這些運算符,您可以在程序中根據(jù)對文件類型的評估結(jié)果執(zhí)行不同的操作:
-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
如果文件可執(zhí)行,則為真
以下示例顯示了此簡單操作的運行情況:
$ 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 發(fā)出同樣的命令 — 如下所示:
$ [ -w evan ]
$ echo $?
0
$ [ -x evan ]
$ echo $?
1
$
同樣,第一個表達式為真,第二個表達式為假 — 正如返回值所指示的那樣。您還可以使用以下命令將兩個文件彼此進行比較:
file1 -ef file2
測試以判斷兩個文件是否與同一個設備相連,是否擁有相同的 inode 編號
file1 -nt file2
測試以判斷第一個文件是否比第二個文件更新(由修改日期決定)
file1 -ot file2
測試以判斷第一個文件是否比第二個文件更舊
以下示例顯示了使用這些運算符比較文件的結(jié)果:
$ [ evan -nt spencer ]
$ echo $?
0
$ [ karen -ot spencer ]
$ echo $?
1
$
名為 evan 的文件比名為 spencer 的文件更新,因而評估為真。類似地,名為 karen 的文件比名為 spencer 的文件更新,因此該評估為假。
字符串比較運算符
如標題所示,這組函數(shù)比較字符串的值。您可以檢查它們是否存在、是否相同或者是否不同。
String
測試以判斷字符串是否不為空
-n string
測試以判斷字符串是否不為空;字符串必須為 test 所識別
-z string
測試以判斷字符串是否為空;字符串必須為 test 所識別
string1 = string2
測試以判斷 string1 是否與 string2 相同
string1 != string2
測試以判斷 string1 是否與 string2 不同
對任何變量進行的最有用的測試之一是判斷它的值是否不為空,可以簡單地將其放在 test 命令行中執(zhí)行這種測試,如下例所示:
$ test "$variable"
強烈建議進行此種測試時用雙引號將變量括住,以讓 shell 識別變量(即使變量為空)。默認情況下執(zhí)行的基本字符串評估和 -n 測試從功能上講是相同的,如以下示例所示:
#example1
if test -n "$1"
then
echo "$1"
fi
執(zhí)行以上例子中的代碼將根據(jù) $1 是否存在給出以下結(jié)果:
$ example1 friday
friday
$
$ example1
$
如果將代碼更改為以下形式,則結(jié)果將相同:
#example2
if test "$1"
then
echo "$1"
fi
如下所示:
$ example2 friday
friday
$
$ example2
$
所有這些表明,通常不需要 -n,它代表默認操作。
要從一個不同的角度來查看各種可能性,您可以用另一個選項來替換 -n,并檢查該值是否為空(相對于非空)。這可以用 -z 選項來實現(xiàn),代碼為:
#example3
if test -z "$1"
then
echo "no values were specified"
fi
運行如下:
$ example3
no values were specified
$ example3 friday
$
如果在沒有命令行參數(shù)的情況下運行該程序,而表達式評估為真,那么將執(zhí)行程序塊中的文本。如果在命令行中有值,則腳本退出,不執(zhí)行任何操作。將評估操作放在腳本的開頭非常有用,這可以在可能產(chǎn)生錯誤的進一步處理之前預先檢查變量值。
其余的字符串運算符對兩個變量/字符串之間的精確匹配或其中的差異(您也可以稱之為等價性和“不等價性”)進行評估。第一個例子對匹配進行測試:
$ 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 類型),在允許腳本運行之前這些值必須匹配。請注意,= 或 != 運算符的優(yōu)先級高于其它大多數(shù)可指定選項,且要求必須伴有表達式。因此,除了比較字符串的選項之外,= 或 != 都不能和檢查某種東西(如可讀文件、可執(zhí)行文件或目錄)的存在性的選項一起使用。
整數(shù)比較運算符
正如字符串比較運算符驗證字符串相等或不同一樣,整數(shù)比較運算符對數(shù)字執(zhí)行相同的功能。如果變量的值匹配則表達式測試為真,如果不匹配,則為假。整數(shù)比較運算符不處理字符串(正如字符串運算符不處理數(shù)字一樣):
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.
$
和字符串一樣,比較的值可以是在腳本外為變量賦的值,而不必總是在命令行中提供。以下示例演示了實現(xiàn)這一點的一種方法:
#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.
$
整數(shù)比較運算符最佳的用途之一是評估指定的命令行變量的數(shù)目,并判斷它是否符合所要求的標準。例如,如果某個特定的命令只能在有三個或更少變量的情況下運行,
#example7 - display variables, up to three
if [ "$#" -gt 3 ]
then
echo "You have given too many variables."
exit $#
fi
只要指定三個或更少的變量,該示例腳本將正常運行(并返回值 0)。如果指定了三個以上的變量,則將顯示錯誤消息,且例程將退出 — 同時返回與命令行中給定的變量數(shù)相等的退出代碼。
對這個過程進行修改可以用來在允許運行報表之前判斷當天是否是本月的最后幾天:
#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,您可以調(diào)用腳本在 15 日到 21 日之間每天的一個指定時間運行,然后使用腳本的第一行檢查 $1(在設置日期之后)的值是否為 Thu。如果為 Thu,那么執(zhí)行剩下的腳本,如果不是,則退出。
而另一個想法可能是,只允許腳本在超過 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 的正常結(jié)果取非。
其余兩個運算符中的第一個是 -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 運算符可以確保代碼只在兩個條件都滿足時才執(zhí)行。相反,只要任何一個表達式測試為真,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。如果發(fā)現(xiàn)用戶等于 emmett,則腳本轉(zhuǎn)至 echo 語句,并跳過其余的檢查。它從不檢查終端是否等于 linux,因為它只需要找到一條為真的語句就可以使整個運算為真。在腳本第二次運行時,它判斷用戶不是 emmett,因此它將檢查并發(fā)現(xiàn)終端確實是 linux。由于一個條件為真,腳本現(xiàn)在轉(zhuǎn)至 echo 命令。為了引出第二條消息,兩個條件都必須為假。
在先前確定時間是否為月末的例子中,可以執(zhí)行類似的檢查來防止用戶試圖在周末運行腳本:
#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:在腳本文件中出現(xiàn)的“邏輯”的最簡單的形式(如本文所有示例中所示)是“if ... then”語句。先前的一個代碼段檢查是否存在一定數(shù)量的變量,然后將這些變量回顯。假設我們對此稍微做一些修改,比如我們想回顯變量,并且每次回顯均減去最左邊的變量,以顯示一個倒的三角形。
雖然這聽起來很簡單,但實際并非如此;這是您在執(zhí)行大規(guī)模處理時想實現(xiàn)的方式:處理第一個變量、轉(zhuǎn)移、處理下一個變量……
出于演示的目的,可以按以下方式撰寫腳本中的重要行:
#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
它將按以下方式執(zhí)行:
$ 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.
$
出于檢查的目的將數(shù)量限制為三個變量的原因是減少在例子中要檢查的行數(shù)。一切都按部就班地進行,雖然它令人難以置信地混亂;用戶因使用了超過程序依設計所能處理的變量數(shù)而得到警告,且腳本退出。如果變量數(shù)為 3 或更少,則運算的核心部分開始執(zhí)行。
回顯變量,執(zhí)行測試以查看另一個變量是否存在。如果另一個變量存在,則執(zhí)行一次轉(zhuǎn)移,回顯該變量,執(zhí)行另一測試,等等。總共使用了 16 個有效行,而程序僅能處理不超過三個變量 — 非常混亂。假設消除變量數(shù)的限制,程序可以處理任意數(shù)量的變量。經(jīng)過一些修改,腳本被縮短(美化)了,并能處理任意數(shù)量的變量:
#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
現(xiàn)在減少到只有 5 個有效行,且消除了第一個腳本三個變量的限制,并在運行時要更高效。
示例 2:無論何時當在腳本內(nèi)執(zhí)行與處理相關(guān)的操作時,下一個操作將始終檢查上一操作的狀態(tài),以確認它已成功完成。您可以通過檢查 $? 的狀態(tài)并驗證它等于 0 來實現(xiàn)這一目的。例如,如果一個數(shù)據(jù)目錄是否能訪問非常重要,
#example13
TEMP=LST
cd $TEMP
if [ $?-ne 0 ]
then
echo "Data directory could not be found."
Exit
fi
處理錯誤
test 命令常常出現(xiàn)的錯誤事實上只有兩種類型。第一種是未使用正確的評估類型,例如將字符串變量與整型變量進行比較或者將帶填充的字符串與不帶填充的字符串進行比較。仔細評估您使用的變量將使您最終找到錯誤的根源,并讓您能夠解決這些問題。
第二種錯誤類型包括將方括號誤認為別名之外的某個東西。方括號與其內(nèi)容之間必須有一個空格;否則,它們將不能解釋其中的對象。例如,
$ [ "$LOGNAME" -gt 9]
test:] missing
$
請注意,錯誤消息指示 test 存在問題,即使使用了別名 ]。這些問題很容易發(fā)現(xiàn),因為錯誤消息準確地將這些問題顯示出來,然后您可以增加必要的空格。
結(jié)論
要在 shell 腳本中構(gòu)建邏輯,您必須添加條件語句。每一條這種語句的核心都是對條件的評估,以判斷它是否存在 — 通過使用 test 命令完成評估。了解它和它的別名(左方括號 ([)的工作原理將使您能夠撰寫可以完成一些復雜操作的 shell 腳本。
本文來自ChinaUnix博客,如果查看原文請點:http://blog.chinaunix.net/u1/49611/showart_572871.html |
|