# 第二十六章. 列表結構
_and 列表_ 和 _or 列表_ 結構提供了連續執行若干命令的方法,可以有效地替換復雜的嵌套 [if/then](http://tldp.org/LDP/abs/html/testconstructs.html#TESTCONSTRUCTS1) ,甚至 [case](http://tldp.org/LDP/abs/html/testbranch.html#CASEESAC1) 語句。
### 鏈接多個命令
**and 列表**
``` command-1 && command-2 && command-3 && ... command-n ```
只要前一個命令返回 _true_(即 0),每一個命令就依次執行。當第一個 _false_(即 非0)返回時,命令鏈條即終止(第一個返回 _false_ 的命令是最后一個執行的)。
在[YongYe](https://github.com/yongye)早期版本的[俄羅斯方塊游戲](http://bash.deta.in/Tetris_Game.sh)腳本里,一個有趣的雙條件 _and 列表_ 用法:
```
equation()
{ # core algorithm used for doubling and halving the coordinates
[[ ${cdx} ]] && ((y=cy+(ccy-cdy)${2}2))
eval ${1}+=\"${x} ${y} \"
}
```
**例 26-1. 使用 _and 列表_ 來測試命令行參數**
```
#!/bin/bash
# and list
if [ ! -z "$1" ] && echo "Argument #1 = $1" && [ ! -z "$2" ] && \
# ^^ ^^ ^^
echo "Argument #2 = $2"
then
echo "At least 2 arguments passed to script."
# 鏈條內的所有命令都返回 true。
else
echo "Fewer than 2 arguments passed to script."
# 鏈條內至少有一個命令返回 false。
fi
# 注意: "if [ ! -z $1 ]" 是好用的,但是宣傳與之等同的
# "if [ -n $1 ]" 并不好用。
# 不過,用引號就能解決問題,
# "if [ -n "$1" ]" 好用(譯者注:原文本行內第一個引號位置錯了)。
# ^ ^ 小心!
# 被測試的變量放在引號內總是最好的選擇。
# 下面的代碼功能一樣,用的是“純粹”的 if/then 語句。
if [ ! -z "$1" ]
then
echo "Argument #1 = $1"
fi
if [ ! -z "$2" ]
then
echo "Argument #2 = $2"
echo "At least 2 arguments passed to script."
else
echo "Fewer than 2 arguments passed to script."
fi
# 比起用“and 列表”要更長、更笨重。
exit $?
```
**例 26-2. 使用 _and 列表_ 來測試命令行參數2**
```
#!/bin/bash
ARGS=1 # 預期的參數數量。
E_BADARGS=85 # 參數數量錯誤時返回的值。
test $# -ne $ARGS && \
# ^^^^^^^^^^^^ 條件 #1
echo "Usage: `basename $0` $ARGS argument(s)" && exit $E_BADARGS
# ^^
# 如果條件 #1 結果為 true (傳遞給腳本的參數數量錯誤),
#+ 那么執行本行剩余的命令,腳本終止。
# 下面的代碼行只有在上面的測試失敗時才執行。
echo "Correct number of arguments passed to this script."
exit 0
# 如果要檢查退出值,腳本終止后運行 "echo $?"。
```
當然,_and 列表_ 也可以給變量設置默認值。
```
arg1=$@ && [ -z "$arg1" ] && arg1=DEFAULT
# 如果有命令行參數,則把參數值賦給 $arg1 。
# 但是... 如果沒有參數,則使用DEFAULT給 $arg1 賦值。
```
**or 列表**
```
command-1 || command-2 || command-3 || ... command-n
```
只要前一個命令返回_false_,每一個命令就依次執行。當第一個_true_ 返回時,命令鏈條即終止(第一個返回_true_ 的命令是最后一個執行的)。很明顯它與“and 列表”相反。
例 26-3. _or 列表_ 與 _and 列表_ 結合使用
```
#!/bin/bash
# delete.sh, 不那么巧妙的文件刪除工具。
# 用法: delete 文件名
E_BADARGS=85
if [ -z "$1" ]
then
echo "Usage: `basename $0` filename"
exit $E_BADARGS # No arg? Bail out.
else
file=$1 # Set filename.
fi
[ ! -f "$file" ] && echo "File \"$file\" not found. \
Cowardly refusing to delete a nonexistent file."
# AND 列表,如果文件不存在則顯示出錯信息。
# 注意,echo 消息內容分成了兩行,中間通過轉義符(\)連接。
[ ! -f "$file" ] || (rm -f $file; echo "File \"$file\" deleted.")
# OR 列表,刪除存在的文件。
# 注意上面的邏輯顛倒。 Note logic inversion above.
# “AND 列表” 在得到 true 時執行, “OR 列表”在得到 false 時執行。
exit $?
```
<img src="http://tldp.org/LDP/abs/images/caution.gif"> 如果 _or 列表_ 第一個命令返回 true,它**會**執行。
```
# ==> 下面的代碼段來自 /etc/rc.d/init.d/single
#+==> 作者 Miquel van Smoorenburg
#+==> 說明了 "and" 和 "or" 列表。
# ==> 帶箭頭的注釋是本文作者添加的。
[ -x /usr/bin/clear ] && /usr/bin/clear
# ==> 如果 /usr/bin/clear 存在, 則調用它。
# ==> 調用命令之前檢查它是否存在,
#+==> 可以避免出錯消息和其他怪異的結果。
# ==> . . .
# If they want to run something in single user mode, might as well run it...
for i in /etc/rc1.d/S[0-9][0-9]* ; do
# 檢查腳本是否存在。
[ -x "$i" ] || continue
# ==> 如果對應的文件在 $PWD 里*沒有*找到,
#+==> 則跳回到循環頂端“繼續運行”。
# 丟棄備份文件和 rpm 生成的文件。
case "$1" in
*.rpmsave|*.rpmorig|*.rpmnew|*~|*.orig)
continue;;
esac
[ "$i" = "/etc/rc1.d/S00single" ] && continue
# ==> 設置腳本名,但先不要執行
$i start
done
# ==> . . .
```
<img src="http://tldp.org/LDP/abs/images/important.gif">
_and 列表_ 或 _or 列表_ 的[退出狀態](http://tldp.org/LDP/abs/html/exit-status.html#EXITSTATUSREF)就是最后一個執行的命令的退出狀態。
聰明地結合 _and 列表_ 和 _or 列表_ 是可能的,但是程序邏輯會很容易地變得令人費解,需要密切注意[操作符優先規則](http://tldp.org/LDP/abs/html/opprecedence.html#OPPRECEDENCE1),而且,會帶來大量的調試工作。
```
false && true || echo false # false
# 下面的代碼結果相同
( false && true ) || echo false # false
# 但這個就不同了
false && ( true || echo false ) # (什么都不顯示)
# 注意語句是從左到右組合和解釋的。
# 通常情況下最好避免這種復雜性。
# 感謝, S.C.
```
[例 A-7](http://tldp.org/LDP/abs/html/contributed-scripts.html#DAYSBETWEEN) 和 [例 7-4](http://tldp.org/LDP/abs/html/fto.html#BROKENLINK) 解釋了用 _and 列表_ / _or 列表_ 來測試變量。
- 第一部分 初見shell
- 1. 為什么使用shell編程
- 2. 和Sha-Bang(#!)一起出發
- 2.1 調用一個腳本
- 2.2 牛刀小試
- 第二部分 shell基礎
- 3. 特殊字符
- 4. 變量與參數
- 4.1 變量替換
- 4.2 變量賦值
- 4.3 Bash弱類型變量
- 4.4 特殊變量類型
- 5. 引用
- 5.1 引用變量
- 5.2 轉義
- 6. 退出與退出狀態
- 7. 測試
- 7.1 測試結構
- 7.2 文件測試操作
- 7.3 其他比較操作
- 7.4 嵌套 if/then 條件測試
- 7.5 牛刀小試
- 8. 運算符相關話題
- 8.1 運算符
- 8.2 數字常量
- 8.3 雙圓括號結構
- 8.4 運算符優先級
- 第三部分 shell進階
- 10. 變量處理
- 10.1 字符串處理
- 10.1.1 使用 awk 處理字符串
- 10.1.2 參考資料
- 10.2 參數替換
- 11. 循環與分支
- 11.1 循環
- 11.2 嵌套循環
- 11.3 循環控制
- 11.4 測試與分支
- 12. 命令替換
- 13. 算術擴展
- 14. 休息時間
- 第五部分 進階話題
- 19. 嵌入文檔
- 20. I/O 重定向
- 20.1 使用 exec
- 20.2 重定向代碼塊
- 20.3 應用程序
- 22. 限制模式的Shell
- 23. 進程替換
- 26. 列表結構
- 25. 別名