以前趕鴨子上架寫過一個不算太複雜的bash腳本,被bash中的條件控制噁心到了,如今抽絲剝繭深刻學習下,防止之後再掉坑裏html
if commands; then
commands
[elif commands; then
commands...]
[else
commands]
fi
複製代碼
bash中的條件控制的基本形式如上, ;
能夠與換行互相替換,這篇文章主要來研究下if後面接着的commandslinux
當命令執行完畢後,命令(包括咱們編寫的腳本和 shell 函數)會給系統發送一個值,叫作退出狀態。 這個值是一個 0 到 255 之間的整數,說明命令執行成功或是失敗。按照慣例,一個零值說明成功,其它全部值說明失敗。 Shell 提供了一個參數 $?
,咱們能夠用它檢查退出狀態。git
[me@linuxbox ~]$ ls -d /usr/bin
/usr/bin
[me@linuxbox ~]$ echo $?
0
[me@linuxbox ~]$ ls -d /bin/usr
ls: cannot access /bin/usr: No such file or directory
[me@linuxbox ~]$ echo $?
2
複製代碼
這是很重要的概念,意味着咱們能夠不只僅使用test命令來進行條件控制,咱們可使用一個普通的命令來做爲判斷條件。github
## 下載某個文件,成功則作一些事情,不成功就不作
if wget xxx ;then;commands;fi
複製代碼
在shell中true和false並不像其餘語言同樣是布爾值,而是兩個內建的命令
它們不作任何事情,除了以一個0或1退出狀態來終止執行。 True 命令老是執行成功,而 false 命令老是執行失敗:正則表達式
[me@linuxbox~]$ true
[me@linuxbox~]$ echo $?
0
[me@linuxbox~]$ false
[me@linuxbox~]$ echo $?
1
複製代碼
常常與 if 一塊使用的命令是 test。它有兩種等價形式shell
test expression
[ expression ] ## expression先後的空格必不可少
複製代碼
這裏的 expression 是一個表達式,其執行結果是 true 或者是 false。當表達式爲真時,這個 test 命令返回一個零 退出狀態,當表達式爲假時,test 命令退出狀態爲1。這句話比較重要,在這裏踩了幾個坑,後面會詳細介紹。
test命令中的expression能夠對文件、字符串和整數的狀態進行判斷express
表達式 | 若是下列條件爲真則返回True |
---|---|
file1 -ef file2 | file1 和 file2 擁有相同的索引號(經過硬連接兩個文件名指向相同的文件)。 |
file1 -nt file2 | file1新於 file2。 |
file1 -ot file2 | file1早於 file2。 |
-b file | file 存在而且是一個塊(設備)文件。 |
-c file | file 存在而且是一個字符(設備)文件。 |
-d file | file 存在而且是一個目錄。 |
-e file | file 存在。 |
-f file | file 存在而且是一個普通文件。 |
-g file | file 存在而且設置了組 ID。 |
-G file | file 存在而且由有效組 ID 擁有。 |
-k file | file 存在而且設置了它的「sticky bit」。 |
-L file | file 存在而且是一個符號連接。 |
-O file | file 存在而且由有效用戶 ID 擁有。 |
-p file | file 存在而且是一個命名管道。 |
-r file | file 存在而且可讀(有效用戶有可讀權限)。 |
-s file | file 存在且其長度大於零。 |
-S file | file 存在且是一個網絡 socket。 |
-t fd | fd 是一個定向到終端/從終端定向的文件描述符 。 |
這能夠被用來決定是否重定向了標準輸入/輸出錯誤。 | |
-u file | file 存在而且設置了 setuid 位。 |
-w file | file 存在而且可寫(有效用戶擁有可寫權限)。 |
-x file | file 存在而且可執行(有效用戶有執行/搜索權限)。 |
一個簡單的例子bash
FILE=~/.bashrc
if [ -e "$FILE" ]; then
if [ -f "$FILE" ]; then
echo "$FILE is a regular file."
fi
if [ -d "$FILE" ]; then
echo "$FILE is a directory."
fi
if [ -r "$FILE" ]; then
echo "$FILE is readable."
fi
if [ -w "$FILE" ]; then
echo "$FILE is writable."
fi
if [ -x "$FILE" ]; then
echo "$FILE is executable/searchable."
fi
else
echo "$FILE does not exist"
exit 1
fi
複製代碼
表達式 | 若是下列條件爲真則返回True |
---|---|
string | string 不爲 null。 |
-n string | 字符串 string 的長度大於零。 |
-z string | 字符串 string 的長度爲零。 |
string1 = string2 | |
string1 == string2 | string1 和 string2 相同。 單或雙等號均可以,不過雙等號更受歡迎。 |
string1 != string2 | string1 和 string2 不相同。 |
string1 > string2 | sting1 排列在 string2 以後。 |
string1 < string2 | string1 排列在 string2 以前。 |
一個例子網絡
ANSWER=maybe
if [ -z "$ANSWER" ]; then
echo "There is no answer." >&2
exit 1
fi
if [ "$ANSWER" = "yes" ]; then
echo "The answer is YES."
elif [ "$ANSWER" = "no" ]; then
echo "The answer is NO."
elif [ "$ANSWER" = "maybe" ]; then
echo "The answer is MAYBE."
else
echo "The answer is UNKNOWN."
fi
複製代碼
表達式 | 若是爲真... |
---|---|
integer1 -eq integer2 | integer1 等於 integer2。 |
integer1 -ne integer2 | integer1 不等於 integer2。 |
integer1 -le integer2 | integer1 小於或等於 integer2。 |
integer1 -lt integer2 | integer1 小於 integer2。 |
integer1 -ge integer2 | integer1 大於或等於 integer2。 |
integer1 -gt integer2 | integer1 大於 integer2。 |
例子socket
INT=-5
if [ -z "$INT" ]; then
echo "INT is empty." >&2
exit 1
fi
if [ $INT -eq 0 ]; then
echo "INT is zero."
else
if [ $INT -lt 0 ]; then
echo "INT is negative."
else
echo "INT is positive."
fi
if [ $((INT % 2)) -eq 0 ]; then
echo "INT is even."
else
echo "INT is odd."
fi
fi
複製代碼
AND -a
OR -o
NOT !
複製代碼
[[ expression ]]
相似於test命令,可是它的功能更爲強大
string1 =~ regex
複製代碼
==
操做符支持模式匹配[me@linuxbox ~]$ FILE=foo.bar
[me@linuxbox ~]$ if [[ $FILE == foo.* ]]; then
> echo "$FILE matches pattern 'foo.*'"
> fi
foo.bar matches pattern 'foo.*'
複製代碼
能夠直接使用&& ||而不用-a -o
[[ ... && ... && ... ]] 和 [ ... -a ... -a ...] 不同,[[ ]] 是邏輯短路操做,而 [ ] 不會進行邏輯短路
test命令只支持數字的比較而不支持 + - * / %
,[[ ... ]]能夠支持
test命令中ASCII比較須要轉義,而[[ ]]中不須要
[ aaa \> bbbb ]
[[ aaa > bbbb ]]
複製代碼
這裏有個須要注意的點, >
或者 \>
比較的ASCII,在比較數字時很容易出錯
例如
[[ "a" != "b" && 10 > 2 ]] ## 10的第一位是1,ASCII值小於2,因此這個表達式的值是false
複製代碼
若是須要進行數字的比較須要使用 -le
等命令選項,或者使用(( ))
除了[[ ]]能夠進行整數運算以外還有幾種其餘的方式
## expr進行數學運算,注意空格,使用*須要轉義\*
expr 2 + 2
## 將運算結果賦值,使用反引號或者$()
s=`expr 2 + 3`
echo $s
5
## 等價爲
s=$[2+3] ## 不用考慮空格。*也不用轉義
## 等價爲
let s=2+3
## 等價爲
s=$((2+3))
## ((expression))能夠用來進行整數的比較
[[ "a" != "b" ]] && ((10 > 2)) ## 整數比較正確的寫法
複製代碼
假設一個變量 ENABLE
被賦值爲true/false,咱們應該怎麼去使用它呢
## 直接使用$ENABLE,通常狀況下沒有問題
## 可是若是ENABLE是個未定義的變量或者空字符串又或者是一個退出狀態爲true的命令,這個if都會判斷爲true
ENABLE=false
if $ENABLE
then
echo true
else
echo false
fi
## ENABLE是一個空字符串
➜ ~ ENABLE=""
➜ ~ if $ENABLE;then;echo true;else;echo false;fi
true
## ENABL爲1
➜ ~ ENABLE=1
➜ ~ if $ENABLE;then;echo true;else;echo false;fi
zsh: command not found: 1
false
## ENABLE=echo $aaaa
➜ user-1678701-1561966146 ENABLE=echo $aaaa
➜ user-1678701-1561966146 echo $ENABLE
echo
➜ user-1678701-1561966146 if $ENABLE;then;echo true;else;echo false;fi
true
複製代碼
爲了不變量未被定義仍被當作true執行,即便有一個變量是true/false,咱們仍須要將它當作字符串來處理
ENABLE=""
if [[ $ENABLE = "true" ]]
then
echo true
else
echo false
fi
複製代碼
若是不使用 [[ ]]
而是 [ ]
會發現當變量未定義時會發生異常
if [ $aaaaaaaa = "true" ]
then
echo true
else
echo false
fi
## 執行結果,這裏雖然也打印出了false,可是是由於$aaaaaaaa = "true"執行失敗,而不是test命令對錶達式的判斷
./hello_world.sh: line 5: [: =: unary operator expected
false
複製代碼
因此建議能用[[ ]] 的地方所有用[[ ]] ,而用 [ ]
時須要在引用變量時再套個雙引號 if [ "$aaaaaaaa" = "true" ]
以前說過在test命令這踩了幾個坑,我碰到的場景是有個布爾值變量,而後根據其餘條件,兩個條件&&操做以後進行判斷
ENABLE=false
TYPE=Debug
if [[ $ENABLE && $TYPE = "Debug" ]]
then
echo true
else
echo false
fi
複製代碼
這種寫法不管 ENABLE
是true仍是false最後都會打印true
嘗試修改下 TYPE
的值,發現打印出了false,因此問題出在前一個語句中
將 [[ $ENABLE ]]
單獨拿出來測試,發現只要ENABLE賦值爲非空值,該條件都爲true
在這裏產生了一個誤解,test命令並不能對true/false自己進行真值判斷。
而 [[ $ENABLE ]] 的真正含義是對變量ENABLE進行非空判斷
正確的用法應該是
## 將$ENABLE變爲test命令能夠正確支持的形式
## 這裏$ENABLE被當作字符串
if [[ $ENABLE = true && $TYPE = "Debug" ]]
## 另外一種方式是將$ENABLE單獨做爲一個命令
## 這裏的$ENABLE是一個命令
if $ENABLE && [[ $TYPE = "Debug" ]]
複製代碼
不少教程上都說在 [[ ]]
要捨得加空格,簡單測試一下
[[ 1 == 2 ]] ## 結果爲false
[[ 1==2 ]] ## 結果爲真
複製代碼
這裏簡單談下本身的理解,未經查證,有誤歡迎指正
[ ]
[[ ]]
均可以看作test命令,而其中的內容均可以看作test命令的參數[[ 1==2 ]]爲真是由於將1==2總體做爲了命令參數
而[[ 1 == 2 ]]則是三個參數,其中==是tes支持的操做符
再聯繫上面的 [[ false ]]
爲true,這裏也是將true/false做爲了一個參數,而不是執行true/false命令
因此建議在寫條件判斷的時候考慮成命令參數,分清楚比較對象和操做符
說回到以前ENABLE和其餘條件結合的例子,當時我想將結合的真值直接從新賦值給ENABLE
相似下面的代碼
ENABLE=true
TYPE=Debug
ENABLE= $($ENABLE && [[ $TYPE = "Debug" ]]) ## ENABLE爲空
複製代碼
失敗的緣由咱們來仔細探究一下
在shell中函數/命令實際是沒法將一個值帶回到調用方的,return的是函數執行的狀態,而命令展開等展開的實際是標準輸出的數據。而true/false/test命令都是沒有標準輸出的。這裏咱們指望的是$ENABLE
執行true命令,在將true值輸出到標準輸出再與[[ $TYPE = "Debug" ]]
的輸出結合,實際狀況並不是如此
寫了個函數
getBoolValue(){
## 將字符串當作命令執行
if eval $*
then
echo true
else
echo false
fi
}
ENABLE=true
TYPE=Debug
## &&須要轉義才能做爲getBoolValue函數的參數
## 不然會被看作getBoolValue $ENABLE做爲一個總體[[ $TYPE = "Debug" ]]做爲一個總體,而後&&操做
ENABLE=$(getBoolValue $ENABLE \&\& [[ $TYPE = "Debug" ]])
echo $ENABLE
複製代碼
固然這個函數只是寫來試驗下,沒有太多的實際價值