test 命令
- POSIX 將 test 的參數描述為「表達式」:
- 一元(unary)表達式:由看似一個選項(如-d)與相對應的運算數組成(基本上是一個文件名)
- 二元(binary)表達式:兩個運算數與一個內嵌的運算元組成,做某種比較操作
- test 命令有另一種形式
[ expression ]
,方括號與表達式一定要以空白隔開 -
表達式可以前置
!
表示否定
在 XSI 兼容的系統裡,-a
意義等同於 &&
; 為了可移植性,建議使用多重條件而非 -a
, -o
1 2 3 4 |
if [ -n "$str" -a -f "$file" ] # 兩種條件都會計算 if [ -n "$str" ] && [ -f "$file" ] # 以快捷方式計算 if [ -n "$str" && -f "file" ] # 錯誤的語法 |
使用 test 的訣竅
- 須有參數(避免 null),因此變量展開都要以引號括起來
- 為了可移植最大化,可以替比較字符串加上前綴 X,例如
if [ "X$answer" = "Xyes" ]
,可以避免字符串為空或開頭帶減號而混淆 test 命令 - test 是可以被愚弄的,例如
test -r a_file && cat_file
,a_file 可能會在執行 test 與執行 cat 之間改變 - 只能做整數測試
範例:檢查輸入參數
1 2 3 4 5 6 7 8 9 10 |
#! /bin/sh if [ $# -ne 1 ] then echo Usage: finduser username >&2 exit 1 fi who | grep $1 |
case 語句
- 比 if/else 更適合用來進行模式匹配
- 圓右括號是 Shell 語法裡唯一的不對稱定界符
- 默認模式(*)結尾的
;;
可加可不加
1 2 3 4 5 6 7 8 9 10 11 12 13 |
case $1 in -f) ... ;; -d) | --directory) ... ;; *) echo $1: unkown option >&2 exit 1 ;; esac |
for 語句
for 循環裡的 in 列表是可選的,如果省略,則會遍歷命令行參數
1 2 3 4 5 6 7 8 9 |
for i # for i in "$@" do case $1 in -f) ... ;; ... esac done |
while 與 until 語句
- while, until 差別僅在於如何透過 condition 來退出
- break, continue 特別具備中斷或繼續多個循環層級的能力,彌補 Shell 語言裡 goto 關鍵字的不足
- break, continue 可接受參數指出要中斷或繼續多少個被包含的循環
1 2 3 4 5 6 7 |
printf "Enter username:" read user until who | grep "$user" > /dev/null do sleep 30 done |
1 2 3 4 5 6 7 8 |
while condition1 # 外部循環 do ... while condition2 # 內部循環 do ... break 2 # 中斷外部循環 done done |
以 shift 處理選項
執行 shift 後,$1 會消失並以 $2 取代,以此類推往後遞進
範例: 選項處理的流程控制
- 傳統上,以
--
結束選項
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
file= verbose= quiet= long= while [ $# -gt 0 ] do case $1 in -f) file=$2 shift ;; -v) verbose=true quiet= ;; -q) quiet=true verbose= ;; -l) long=true ;; --) shift break ;; -*) echo $0: $1: unrecognized option >&2 ;; *) break ;; esac shift done |
getopts
- 用來簡化選項處理
- 用一個字串表示合法選項,如果選項字母後面跟著冒號(:),代表需要一個參數
- 一旦遇到帶參數的選項,getops 會將參數值設置到變量
OPTARG
- 變量
OPTIND
代表下一個要處理的參數的索引值
與上方範例比較:
- 不須開頭的減號
- 自動處理
--
的情況 - 自動顯示非法選項的錯誤訊息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
file= verbose= quiet= long= while getopts f:vql opt do case $opt in f) file=$OPTARG ;; v) verbose=true quiet= ;; q) quiet=true verbose= ;; l) long=true ;; esac done shift $((OPTIND -1)) # 刪除選項,留下參數 |
將冒號置於選項字串作為第一個字元,可以改變 getopts 的行為:不顯示任何錯誤訊息,不合法的選項可在 '?'
情況裡自行處理
函數
- 必須事先定義,可置於腳本起始處,或放在另一個文件以點號(.)命令取用
- 函數中的參數獨立於父腳本,函數執行時,父腳本參數會臨時被覆蓋(shadowed)或隱藏
- 使用
exit
會終止整個腳本;使用return
則返回一個退出值父腳本,將函數設計成返回{0,1}可取代 test - 若未在 return 提供參數,則預設為最後一個執行命令的退出碼,嚴謹的寫法為
return $?
- POSIX Shell 的函數沒有提供局部變量,所有函數都與父腳本共享變量
標準輸入、輸出、錯誤輸出
- 程序應有一個數據來源、數據出口以及報告問題的地方
- 程序不知道也不在意輸入輸出背後是哪些設備,可以預期的是,在程序啟動時,這些標準位置都已經準備好被使用了
- 這樣的程序可以被稱為 filter,因為它們過濾了數據流
read
將訊息讀入成一個或多個 shell 變量
任何命令都能將輸出透過管道傳送給 read,在循環的情況下特別有用
範例: 逐行
讀取 /etc/passwd
1 2 3 4 5 |
while IFS=: read user pass uid gid fullname homedir usershell do ... done < /etc/passwd |
也可以寫成這樣
1 2 3 4 5 6 |
cat /etc/passwd | while IFS=: read user passwd uid gid fullname homedir usershell do ... done |
範例:查找、取代並建立多個目錄
1 2 3 4 5 6 7 |
find /home/tolstoy -type d -print | sed 's;/home/tolstoy/;/home/lt/;' | while read newdir do mkdir newdir done |
文件描述符
- UNIX 以一個小的整數表示每個進程的打開文件,成為文字描述符(file descriptors)
-
傳統上,Shell 允許處理最多 10 個打開文件,描述符從 0 至 9
-
描述符 0、1、2 各自對應標準輸入、輸出、錯誤輸出
-
1>
裡的 1 其實沒有必要,供輸出重定向的默認文件描述符是標準輸出 -
2>&1
中的 &1 語法代表「無論描述符1在哪裡」,意思是將標準錯誤輸出定向至標準輸出,這四個字符必須連在一起不能有空格,另外須注意使用的順序123make > results 2>&1make 2>&1 > results exec
命令能改變 Shell 本身的 I/O1234exec 2> /tmp/$0.logexec 3< /some/file # 打開新文件描述符read name rank serno <&3 # 從該文件讀取- 暫存文件描述符,以便在變更後回復
123456exec 5>&2 # 將原有標準錯誤輸出保存到 fd5exec 2> /tmp/$0.log # 更改標準錯誤輸出...exec 2>&5 # 復原原有標準錯誤輸出exec 5>&- # 刪除 fd5
exec 命令
- 除了上述改變 I/O 設置的用法,另一個用法是在當前進程中啟動新程序
-
為單向操作,也就是說控制權不可能回到原腳本,唯一的例外是新程序無法被正常調用
123exec real-app -q "$qargs" -f "$fargs" "$@"echo real-app failed, get help! 1>&2
printf 命令
- 默認情況下,轉義序列如
\n
、\r
只在格式字串被解釋,對參數字串不解釋,除非使用%b
格式 - 可用來指定輸出字段的寬度、精度與對齊操作,格式為
%{flags}{width}.{precision}{format-specifier}
,可透過變量值動態指定
範例:對齊操作
1 2 3 4 5 |
$ printf "|%10s|\n" hello | hello| $ printf "|%-10s|\n" hello |hello | |
範例:精度操作
1 2 3 4 5 |
$ printf "%.10s\n" "a very long string" a very lon $ printf "%.2f\n" 3.1415926535 3.14 |
範例:總是帶上正負號
1 2 3 |
$ printf "%+d %+d\n" 1 -1 +1 -1 |
範例:自動補0
1 2 3 |
$ printf "%05d\n" 1 00001 |