test簡介 測試命令test用於形成一個表達式,結合條件判斷語句if-else來判斷。 例如可以判斷某個文件是否存在,是否具備什麼樣的特性(可讀嗎?可寫嗎?可執行嗎?塊文件嗎?)等等。 測試命令test有三種語法格式: test EXPRESSION [ EXPRESSION ] [[ EXPRE ...
test簡介
測試命令test用於形成一個表達式,結合條件判斷語句if-else來判斷。
例如可以判斷某個文件是否存在,是否具備什麼樣的特性(可讀嗎?可寫嗎?可執行嗎?塊文件嗎?)等等。
測試命令test有三種語法格式:
test EXPRESSION
[ EXPRESSION ]
[[ EXPRESSION ]]
前兩種是等價的,應該是沒有區別的。註意EXPRESSION兩邊與中括弧之間需要有空格。
雙中括弧與前兩者的區別,主要在於表達式中的操作符如果是這種情況的時候。
[[ string1 > string2 ]]
[[ string1 < string2 ]]
字元串之間比較比較大小,其實比的是在詞典中字元的先後順序。[]應該是基於ASCII來比較,而[[]]應該是基於shell當前的地理位置設置來比較(應該與locale相關的環境變數有關吧)。
更多的信息,可能需要大家去直接看bash的手冊吧,我稍微看了下,是真的很難以理解,所以暫時放棄了,只找到了這些簡單的區別,按照駿馬兄的話,咱只能先學習bash的形了,直接跳著看bash手冊會有一種被勸退的感覺。
測試表達式的結果為真或者假,真返回0,假返回1。這個返回值不會顯示在終端上,而是保存在shell的特殊變數“$?”中(bash原文中提到的是特殊參數(parameter),變數是一個通過名稱表示的參數)。
因此我們只需要在每次測試後echo這個特殊變數的值就可以驗證了。
# [ EXPRESSION ] # echo $?
test實戰
數值比較
-eq:equal,是否相等。
[root@c7-server ~]# age=28 [root@c7-server ~]# [ $age -eq 28 ] [root@c7-server ~]# echo $? 0
-ne:not equal,是否不相等。
[root@c7-server ~]# [ $age -ne 28 ] [root@c7-server ~]# echo $? 1
-gt:greater than,是否大於。
[root@c7-server ~]# [ $age -gt 28 ] [root@c7-server ~]# echo $? 1
-ge:greater equal,是否大於等於。
[root@c7-server ~]# [ $age -ge 28 ] [root@c7-server ~]# echo $? 0
-lt:less than,是否小於。
[root@c7-server ~]# [ $age -lt 28 ] [root@c7-server ~]# echo $? 1
-le:less equal,是否小於等於。
[root@c7-server ~]# [ $age -le 28 ] [root@c7-server ~]# echo $? 0
字元串比較
==:是否等於。
[root@c7-server ~]# name=alongdidi
[root@c7-server ~]# [ $name == alongdidi ] [root@c7-server ~]# echo $? 0
!=:是否不等於。
[root@c7-server ~]# [ $name != alongdidi ] [root@c7-server ~]# echo $? 1
=~:基於regex3的擴展正則匹配,並且使用這個操作符的時候必須使用[[]]。
[root@c7-server ~]# [[ $name =~ along(di){1} ]] [root@c7-server ~]# echo $? 0 [root@c7-server ~]# [ $name =~ along(di){1} ] -bash: syntax error near unexpected token `(' [root@c7-server ~]# [ $name =~ along\(di\){1} ] -bash: [: =~: binary operator expected
-z:判斷字元串是否為空。
[root@c7-server ~]# [ -z $name ] [root@c7-server ~]# echo $? 1
-n:判斷字元串是否不空。
[root@c7-server ~]# [ -n $name ] [root@c7-server ~]# echo $? 0
在做字元串的等值比較時,無論是==、!=或者=~,它們都是二元(binary)的運算符,也就是在這個符號的左右兩邊都必須存在字元串。
如果其中一邊為空的話,就會報錯。
[root@c7-server ~]# unset name [root@c7-server ~]# [ $name == tom ] -bash: [: ==: unary operator expected
因為$name為空,因此整個表達式就變成了。
[ == tom ]
所以bash會報錯並告訴你我們期待的是一元(unary)運算符(因為只有tom這個字元串)。一元也可以叫單目,是一樣的意思。
解決的辦法有三個,對$name使用雙引號或者單引號包裹或者將[]換成[[]]。
[root@c7-server ~]# [ "$name" == tom ] [root@c7-server ~]# echo $? 1 [root@c7-server ~]# [ '$name' == tom ] [root@c7-server ~]# echo $? 1 [root@c7-server ~]# [[ $name == tom ]] [root@c7-server ~]# echo $? 1
文件測試
存在性
-a FILE或者-e FILE:判斷文件是否存在。
[root@c7-server ~]# [ -a /etc/passwd ] [root@c7-server ~]# echo $? 0 [root@c7-server ~]# [ -e /etc/passwd ] [root@c7-server ~]# echo $? 0
文件類型
-b FILE:文件是否存在且為塊設備文件。
[root@c7-server ~]# ls -l /dev/sda1 brw-rw---- 1 root disk 8, 1 Jan 2 13:51 /dev/sda1 [root@c7-server ~]# [ -b /dev/sda1 ] [root@c7-server ~]# echo $? 0
-c FILE:文件是否存在且為字元設備文件。
[root@c7-server ~]# ls -l /dev/autofs crw------- 1 root root 10, 235 Jan 2 13:51 /dev/autofs [root@c7-server ~]# [ -c /dev/autofs ] [root@c7-server ~]# echo $? 0
-d FILE:文件是否存在且為目錄文件。
[root@c7-server ~]# ls -ld /root dr-xr-x---. 16 root root 4096 Jan 7 09:54 /root [root@c7-server ~]# [ -d /root ] [root@c7-server ~]# echo $? 0
-f FILE:文件是否存在且為普通文件(即文本文件)。
[root@c7-server ~]# ls -l /etc/passwd -rw-r--r-- 1 root root 2296 Nov 11 14:28 /etc/passwd [root@c7-server ~]# file /etc/passwd /etc/passwd: ASCII text [root@c7-server ~]# [ -f /etc/passwd ] [root@c7-server ~]# echo $? 0
-h FILE:文件是否存在且為字元鏈接文件,即軟連接、符號鏈接。
-L FILE:同上。
[root@c7-server ~]# ls -l /etc/rc.local lrwxrwxrwx. 1 root root 13 Oct 17 14:59 /etc/rc.local -> rc.d/rc.local [root@c7-server ~]# [ -h /etc/rc.local ] [root@c7-server ~]# echo $? 0 [root@c7-server ~]# [ -L /etc/rc.local ] [root@c7-server ~]# echo $? 0
-p FILE:文件是否存在且為命名管道文件。
[root@c7-server ~]# ls -l /run/dmeventd-client prw------- 1 root root 0 Jan 7 09:54 /run/dmeventd-client [root@c7-server ~]# [ -p /run/dmeventd-client ] [root@c7-server ~]# echo $? 0
-S FILE:文件是否存在且為套接字文件。
[root@c7-server ~]# ls -l /run/systemd/shutdownd srw------- 1 root root 0 Jan 7 09:54 /run/systemd/shutdownd [root@c7-server ~]# file /run/systemd/shutdownd /run/systemd/shutdownd: socket [root@c7-server ~]# [ -S /run/systemd/shutdownd ] [root@c7-server ~]# echo $? 0
文件許可權
-r FILE:文件是否存在且對當前用戶可讀。
-w FILE:文件是否存在且對當前用戶可寫。
-x FILE:文件是否存在且對當前用戶可執行。
[root@c7-server ~]# ls -l /etc/passwd -rw-r--r-- 1 root root 2296 Nov 11 14:28 /etc/passwd [root@c7-server ~]# [ -r /etc/passwd ] [root@c7-server ~]# echo $? 0 [root@c7-server ~]# [ -w /etc/passwd ] [root@c7-server ~]# echo $? 0 [root@c7-server ~]# [ -x /etc/passwd ] [root@c7-server ~]# echo $? 1
特殊文件許可權
-u FILE:文件是否存在且具有SUID許可權。
[root@c7-server ~]# ls -l /usr/bin/passwd -rwsr-xr-x. 1 root root 27832 Jun 10 2014 /usr/bin/passwd [root@c7-server ~]# [ -u /usr/bin/passwd ] [root@c7-server ~]# echo $? 0
-g FILE:文件是否存在且具有SGID許可權。
[root@c7-server ~]# ls -l /usr/bin/wall -r-xr-sr-x. 1 root tty 15344 Jun 10 2014 /usr/bin/wall [root@c7-server ~]# [ -g /usr/bin/wall ] [root@c7-server ~]# echo $? 0
-k FILE:文件是否存在且具有STICKY許可權。
[root@c7-server ~]# ls -ld /tmp/ drwxrwxrwt. 11 root root 4096 Jan 7 14:21 /tmp/ [root@c7-server ~]# [ -k /tmp/ ] [root@c7-server ~]# echo $? 0
文件是否有內容測試
-s FILE:文件是否存在且有內容。
[root@c7-server ~]# ls -l test.txt ls: cannot access test.txt: No such file or directory [root@c7-server ~]# touch test.txt [root@c7-server ~]# [ -s test.txt ] [root@c7-server ~]# echo $? 1 [root@c7-server ~]# [ -s /etc/passwd ] [root@c7-server ~]# echo $? 0
時間戳測試
-N FILE:文件自身從上一次讀操作後是否被修改過。
[root@c7-server ~]# cat test.txt [root@c7-server ~]# [ -N test.txt ] [root@c7-server ~]# echo $? 1 [root@c7-server ~]# echo "alongdidi" > test.txt [root@c7-server ~]# [ -N test.txt ] [root@c7-server ~]# echo $? 0
從屬關係測試
-O FILE:當前用戶是否為文件的屬主。
-G FILE:當前用戶是否屬於文件的屬組。
[root@c7-server ~]# ls -ld /home/zwl/ drwx------. 3 zwl zwl 78 Apr 11 2018 /home/zwl/ [root@c7-server ~]# [ -O /home/zwl/ ] [root@c7-server ~]# echo $? 1 [root@c7-server ~]# [ -G /home/zwl/ ] [root@c7-server ~]# echo $? 1
雙目測試
FILE1 -ef FILE2:FILE1和FILE2是否指向同一個文件系統的相同inode的硬鏈接。
[root@c7-server ~]# ln test.txt test.hard [root@c7-server ~]# ls -li test.txt test.hard 33731123 -rw-r--r-- 2 root root 10 Jan 7 14:31 test.hard 33731123 -rw-r--r-- 2 root root 10 Jan 7 14:31 test.txt [root@c7-server ~]# [ test.txt -ef test.hard ] [root@c7-server ~]# echo $? 0
FILE1 -nt FILE2:FILE1是否新於FILE2,根據文件的mtime。
FILE1 -ot FILE2:FILE1是否舊於FILE2,根據文件的mtime。
[root@c7-server ~]# [ test.txt -nt /etc/passwd ] [root@c7-server ~]# echo $? 0 [root@c7-server ~]# [ test.txt -ot /etc/passwd ] [root@c7-server ~]# echo $? 1
組合測試條件
即與或非。
[ EXP1 -a EXP2 ]:EXP1和EXP2必須都為true,結果才為true。
[ EXP1 -o EXP2 ]:只要EXP1和EXP2當中有一個為true,結果就為true。
[ ! EXP ]:當EXP為true的時候,結果為false;當EXP為false的時候,結果為true。
練習
如果主機名為空或者包含local字元串,則將主機名設置為www.alongdidi.com。
hostName=$(hostname) [ -z "${hostName}" -o ${hostName}=~"local" ] && hostname www.magedu.com
註:實際我在CentOS 7,Bash 4.2.46場景下執行該命令,得到的結果不太對。主要問題出在“=~”的判斷上。暫時未知如何解決,這裡大概知道下思路即可。
命令/腳本狀態返回值
上文中我們介紹了特殊變數$?,它存儲了測試表達式的測試結果。true=0,false=1。
命令執行的結果也會有這麼一個返回值(也可以叫退出狀態碼),一般返回值0表示命令執行成功,返回值非0(多數情況下是1)則表示失敗。
PS:這裡也需要註意,大多數編程語言使用1來表示成功/true等。還有大家也要註意和命令執行後的標準輸出或者標準錯誤輸出區別開。一個表示命令執行成功與否的結果,另一個則是命令執行的輸出結果。
這個返回值在我們執行腳本的時候,也會返回。預設腳本執行的返回值使用的是腳本中最後一條命令的返回值。
如果腳本中前幾條命令的執行均成功了,但是最後一條執行失敗了,那麼整個腳本的$?也是非0的。
我們可以通過exit命令來手工配置退出狀態碼。bash遇到exit會立即退出當前的shell並將返回值存入父shell的$?變數中。因此可以用來立即退出bash腳本。
[root@c7-server ~]# bash [root@c7-server ~]# exit 10 exit [root@c7-server ~]# echo $? 10
PS:有的時候退出狀態碼會異常,可能和返回值的取值有關係。
[root@c7-server ~]# exit 1000 exit [root@c7-server ~]# echo $? 232 [root@c7-server ~]# bash [root@c7-server ~]# exit 1024 exit [root@c7-server ~]# echo $? 0
自定義返回值一般用於bash腳本中的判斷。比如,當某個文件不存在的時候,立即執行exit 5。
腳本會立刻退出,5這個返回值會被返回。一般程式員會事先定義好不同的返回值表達的不同含義,並將其寫入文檔。
用戶根據返回值和該文檔來判斷腳本為什麼中斷執行了。
像rsync的man手冊中就有定義。
0 Success 1 Syntax or usage error 2 Protocol incompatibility 3 Errors selecting input/output files, dirs ...
腳本中的參數
在學習C語言的時候,我們可以向函數執行傳遞參數的操作。bash腳本編程也是可以的。
可以對腳本進行傳參,也可以對函數。
在引用參數的時候,具體是引用腳本的參數還是函數的參數則取決於引用的位置。
按照馬哥課程的進度,函數還未學習到,因此這裡就不說了。(雖然已經有bash編程的基礎)等到寫bash函數的博文時,會在提及。
向腳本傳參和腳本中引用參數十分簡單,示例如下。
[root@c7-server ~]# cat test.sh #!/bin/bash echo $1 echo $2 [root@c7-server ~]# bash test.sh along didi along didi
在執行腳本時,腳本名稱後面的字元串就是參數,多個參數之間以空格分離,根據參數出現在腳本名稱後的位置,在腳本中使用$1、$2、$3...來引用,它們也被稱作位置參數。
shift not shit
如果我們想要改變位置參數的位置,就需要使用到shift內置命令。
shift [n]
shift的本意是移動,我們可以理解為拿掉位置參數最左邊的n個。預設是1個。
[root@c7-server ~]# cat test.sh #!/bin/bash echo $* shift 2 echo $* [root@c7-server ~]# bash test.sh a long di di a long di di di di
其他特殊變數
$#:獲取腳本被傳遞的參數的個數。
[root@c7-server ~]# cat test.sh #!/bin/bash echo $# [root@c7-server ~]# bash test.sh a l on g did i 6
$0:獲取腳本的名稱,這個名稱是執行時的名稱。執行的方式不同,值也不同。
[root@c7-server ~]# cat test.sh #!/bin/bash echo $0 [root@c7-server ~]# bash test.sh test.sh [root@c7-server ~]# /root/test.sh /root/test.sh [root@c7-server ~]# ./test.sh ./test.sh
只想獲取腳本的名稱的話,可結合basename命令。
[root@c7-server ~]# basename $(bash test.sh) test.sh [root@c7-server ~]# basename $(/root/test.sh) test.sh [root@c7-server ~]# basename $(./test.sh) test.sh
$*:引用所有的參數。在雙引號的情況下,所有參數整合作為一個整體。
$@:引用所有的參數。無論是否有雙引號,每個參數自身都作為一個整體。
[root@c7-server ~]# cat test.sh #!/bin/bash echo $* echo $@ [root@c7-server ~]# bash test.sh a long di di a long di di a long di di
區別的話,可以看這個例子。
[root@c7-server ~]# cat test.sh #!/bin/bash for i in $*; do echo $i; done for i in $@; do echo $i; done echo "I am cut-off line" for i in "$*"; do echo $i; done for i in "$@"; do echo $i; done [root@c7-server ~]# bash test.sh a long di di a long di di a long di di I am cut-off line a long di di a long di di
更深度的解釋,查閱官方文檔的話,需要對bash的單詞分割(word splitting)和IFS變數有理解才行。