linux應用之test命令詳細解析
test命令用法
功能:檢查文件和比較值
1)判斷表達式 | |
if test (表達式爲真) | |
if test !表達式爲假 | |
test 表達式1 –a 表達式2 | 兩個表達式都爲真 |
test 表達式1 –o 表達式2 | 兩個表達式有一個爲真 |
2)判斷字符串 | |
test –n 字符串 | 字符串的長度非零 |
test –z 字符串 | 字符串的長度爲零 |
test 字符串1=字符串2 | 字符串相等 |
test 字符串1!=字符串2 | 字符串不等 |
3)判斷整數 | |
test 整數1 –eq 整數2 | 整數相等 |
test 整數1 –ge 整數2 | 整數1大於等於整數2 |
test 整數1 –gt 整數2 | 整數1大於整數2 |
test 整數1 –le 整數2 | 整數1小於等於整數2 |
test 整數1 –lt 整數2 | 整數1小於整數2 |
test 整數1 –ne 整數2 | 整數1不等於整數2 |
4)判斷文件 | |
test File1 –ef File2 | 兩個文件具有同樣的設備號和i結點號 |
test File1 –nt File2 | 文件1比文件2 新 |
test File1 –ot File2 | 文件1比文件2 舊 |
test –b File | 文件存在並且是塊設備文件 |
test –c File | 文件存在並且是字符設備文件 |
test –d File | 文件存在並且是目錄 |
test –e File | 文件存在 |
test –f File | 文件存在並且是正規文件 |
test –g File | 文件存在並且是設置了組ID |
test –G File | 文件存在並且屬於有效組ID |
test –h File | 文件存在並且是一個符號鏈接(同-L) |
test –k File | 文件存在並且設置了sticky位 |
test –b File | 文件存在並且是塊設備文件 |
test –L File | 文件存在並且是一個符號鏈接(同-h) |
test –o File | 文件存在並且屬於有效用戶ID |
test –p File | 文件存在並且是一個命名管道 |
test –r File | 文件存在並且可讀 |
test –s File | 文件存在並且是一個套接字 |
test –t FD | 文件描述符是在一個終端打開的 |
test –u File | 文件存在並且設置了它的set-user-id位 |
test –w File | 文件存在並且可寫 |
test –x File | 文件存在並且可執行 |
每一種條件語句的基礎都是判斷什麼是真什麼是假。是否瞭解其工作原理將決定您編寫的是質量一般的腳本還是您將引以爲榮的腳本。
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 $?
$ [ 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
這些值可以在腳本中使用,就像它們是在命令行中輸入的一樣。請注意,退出命令再次返回一個值 — 在這種情況下,返回的值是從 ? 的值。
一種類似的想法可能是撰寫一個只在每個月的第三個星期三運行的腳本。第三個星期三一定在該月的 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 $?
$ [ "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