##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是不存在的:
```shell
$ 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,其命令格式有兩種:
```shell
test expression
```
或者
```shell
[ 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;
例如:
```shell
[ -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目前沒有定義,或被定義為空字符串的話,
那如下的用法將會失敗:
```shell
$ unset A
$ [ $A = abc ]
[: =: unary oprator expected
```
這是因為命令行碰到$這個meta時,會替換$A的值,
然后,再重組命令行,那就變成了:
`[ = abc ]`,
如此一來,=的左邊就沒有字符串存在了,
因此,造成test的語法錯誤。
但是,下面這個寫法則是成立的。
```shell
$ [ "$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)的條件下執行。
以例子來說好了:
```shell
$ 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所引起的。
事實上,我們在同一個命令行中,可用多個`&&` 或 `||` 來組建呢。
```shell
$ 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!
```shell
$ A=123
$ [ -n "$A" ] && [ "$A" -lt 100 ] || echo 'too big!'
$ too big!
```
若我取消A,照理說,應該不會輸出文字啊,(因為第一個條件不成立)。
```shell
$ unset A
$ [ -n "$A" ] && [ "$A" -lt 100 ] || echo 'too big!'
$ too big!
```
為何上面的結果也可得到呢?
又如何解決呢?
> **Tips:**
>修改的方法有很多種,
>其中一種方法可以利用第7章中介紹過 `command group`...
快告訴我答案,其余免談....
解決方法1:`sub-shell`:
```shell
$ unset A
$ [ -n "$A" ] && ( [ "$A" -lt 100 ] || echo 'too big!' )
```
解決方法二:`command group`:
```shell
$ unset A
$ [ -n "$A" ] && { [ "$A" -lt 100 ] || echo 'too big!'}
```