shell十三問之10:&& 與 || 差在哪?


好不容易,進入了兩位數的章節了... 一路走來,很辛苦吧?也很快樂吧? ^_^

在解答本章題目之前,先讓我們瞭解一個概念: return value。

我們在shell下跑的每一個command或function, 在結束的時候都會傳回父進程一個值,稱為 return value

在shell command line中可用$?, 這個變量得到最"新"的一個return value, 也就是剛剛結束的那個進程傳回的值。

Return Value(RV)的取值為0-255之間, 由進程或者script的作者自行定義:

  • 若在script裡,用exit RV 來指定其值; 若沒有指定, 在結束時,以最後一個命令的RV,為script的RV值。

  • 若在function裡,則用return RV 來代替exit RV即可。

Return Value的作用:用來判斷進程的退出狀態(exit status). 進程的退出狀態有兩種:

  • 0值為"真"(true)
  • 非0值為"假"(false)

舉個例子來說明好了: 假設當前目錄內有一個my.file的文件, 而no.file是不存在的:

$ touch my.file
$ ls my.file
$ echo $? #first echo
0
$ ls no.file
ls: no.file: No such file or directory
$ echo $?     #second echo
1
$ echo $?     #third echo
0

上例的:

  • 第一個echo是關於ls my.file的RV,可得到0的值,因此為true。
  • 第二個echo是關於ls no.file的RV,得到非0的值,因此為false。
  • 第三個echo是關於echo $?的RV,得到0值, 因此為true。

請記住: 每一個command在結束時,都會返回return value,不管你跑什麼命令... 然而,有一個命令卻是“專門”用來測試某一條而返回return value, 以供true或false的判斷, 它就是test命令。

若你用的是bash, 請在command line下, 打man test,或者 man bash 來瞭解這個test的用法。 這是你可用作參考的最精準的文件了,要是聽別人說的,僅作參考就好...

下面,我只簡單作一些輔助說明,其餘的一律以 man為準: 首先,test的表達式,我們稱為expression,其命令格式有兩種:

test expression

或者

[ expression ]

Note:

請務必注意 [] 之間的空白鍵!

用哪一種格式無所謂,都是一樣的效果。 (我個人比較喜歡後者...)

其次,bash的test目前支持的測試對象只有三種:

  • string:字符串,也就是純文字。
  • integer:整數(0或正整數、不含負數或小數)
  • file: 文件

請初學者,一定要搞清楚這三者的差異, 因為test所使用的expression是不一樣的。

以A=123這個變量為例:

  • [ "$A" = 123 ] #是字符串測試,測試$A是不是1、2、3這三個字符。

  • [ "$A" -eq 123 ] #是整數測試,以測試$A是否等於123.

  • [-e "$A" ] #文件測試,測試123這份文件是否存在.

第三, 當expression測試為“真”時, test就返回0(true)的return value; 否則,返回非0(false).

若在 expression 之前加一個!(感嘆號),則在expression為假時,return value為0, 否則, return value 為非0值。

同時,test也允許多重複合測試:

  • expression1 -a expression2 #當兩個expression都為true,返回0,否則,返回非0;
  • expression1 -o expression2 #當兩個expression均為false時,返回非0,否則,返回0;

例如:

[ -d "$file"  -a  -x "$file" ]

表示當$file是一個目錄,且同時具有x權限時,test才會為true。

第四,在command line中使用test時,請別忘記命令行的“重組”特性, 也就是在碰到meta時,會先處理meta,在重新組建命令行。 (這個概念在第2章和第4章進行了反覆強調)

比方說, 若test碰到變量或者命令替換時, 若不能滿足 expression的格式時,將會得到語法錯誤的結果。

舉例來說好了:

關於[ string1 = string2 ]這個test格式, 在等號兩邊必須要有字符串,其中包括空串(null串,可用soft quote或者hard quote取得)。

假如$A目前沒有定義,或被定義為空字符串的話, 那如下的用法將會失敗:

$ unset A
$ [ $A = abc ]
[: =: unary oprator expected

這是因為命令行碰到$這個meta時,會替換$A的值, 然後,再重組命令行,那就變成了: [ = abc ], 如此一來,=的左邊就沒有字符串存在了, 因此,造成test的語法錯誤。 但是,下面這個寫法則是成立的。

$ [ "$A" = abc ]
$ echo $?
1

這是因為命令行重組後的結果為: [ "" = abc ], 由於等號的左邊我們用soft quote得到一個空串, 而讓test的語法得以通過...

讀者諸君,請務必留意這些細節哦, 因為稍一不慎,將會導致test的結果變了個樣。 若您對test還不是很有經驗的話, 那在使用test時,不妨先採用如下這一個"法則":

若在test中碰到變量替換,用soft quote是最保險的*

若你對quoting不熟的話,請重新溫習第四章的內容吧...^_^

okay, 關於更多的test的用法,老話一句:請看其man page (man test)吧!^_^

雖然洋洋灑灑讀了一大堆,或許你還在嘀咕...那...那個return value有啥用?

問得好: 告訴你:return value的作用可大了, 若你想要你的shell變"聰明"的話,就全靠它了: 有了return value, 我們可以讓shell根據不同的狀態做不同的事情...

這時候,才讓我來揭曉本章的答案吧~~~~^_^

&&|| 都是用來"組建" 多個command line用的;

  • command1 && command2 # command2只有在command1的RV為0(true)的條件下執行。
  • command1 || command2 # command2 只有在command1的RV為非0(false)的條件下執行。

以例子來說好了:

$ A=123
$ [ -n "$A" ] && echo "yes! it's true."
yes! it's true.
$ unset A
$ [ -n "$A" ] && echo "yes! it's true."
$ [ -n "$A" ] || echo "no, it's Not true."
no, it's Not true

Note:

[ -n string ]是測試string長度大於0, 則為true。

上例中,第一個&&命令之所以會執行其右邊的echo命令, 是因為上一個test返回了0的RV值; 但第二個,就不會執行,因為test返回了非0的結果... 同理,||右邊的echo會被執行,卻正是因為左邊的test返回非0所引起的。

事實上,我們在同一個命令行中,可用多個&&|| 來組建呢。

$ A=123
$ [ -n "$A" ] && echo "yes! it's true." || echo "no, it's Not ture."
yes! it's true.
$ unset A
$ [ -n "$A" ] && echo "yes! it's true." || echo "no, it's Not ture."
no, it's Not true

怎樣,從這一刻開始,你是否覺得我們的shell是“很聰明”的呢? ^_^

好了,最後佈置一道練習題給大家做做看: 下面的判斷是:當$A被賦值時,在看看其是否小於100,否則輸出too big!

$ A=123
$ [ -n "$A" ] && [ "$A" -lt 100 ] || echo 'too big!'
$ too big!

若我取消A,照理說,應該不會輸出文字啊,(因為第一個條件不成立)。

$ unset A
$ [ -n "$A" ] && [ "$A" -lt 100 ] || echo 'too big!'
$ too big!

為何上面的結果也可得到呢? 又如何解決呢?

Tips:

修改的方法有很多種, 其中一種方法可以利用第7章中介紹過 command group...

快告訴我答案,其餘免談....

解決方法1:sub-shell

$ unset A
$ [ -n "$A" ] && ( [ "$A" -lt 100 ] || echo 'too big!' )

解決方法二:command group:

$ unset A
$ [ -n "$A" ] && { [ "$A" -lt 100 ] || echo 'too big!'}