寫shell腳本的時候,通過while、case、shift來設計腳本的指令行選項是一件比較麻煩的事,因為Unix指令行的選項和參數自由度很高,支援短選項和長選項,參數可能是可選的,選項順序可能是無所謂的,等等。
bash下的getopt指令可以解析指令行的選項和參數,将散亂、自由的指令行選項和參數進行改造,得到一個完整的、規範化的參數清單,這樣再使用while、case和shift進行處理就簡單的太多了。
getopt有不同的版本,本文介紹的是它的增強版(enhanced),相比傳統的getopt(也成為相容版本的getopt),它提供了引号保護的能力。另外,除了不同版本的getopt,bash還有一個内置指令getopts(注意,有個尾随的字元s),也用來解析指令行選項,但隻能解析短選項。
要驗證安裝的getopt是增強版的還是傳統版的,使用
getopt -T
判斷即可。如果它什麼都不輸出,則是增強版,此時它的退出狀态碼為4。如果輸出"--",則是傳統版的getopt,此時它的退出狀态碼為0。如果想在腳本中進行版本檢查,可以參考如下代碼: getopt -T &>/dev/null;[ $? -ne 4 ] && { echo "not enhanced version";exit 1; }
1.指令行選項的那些事
在學習getopt如何使用之前,必須先知道指令行的一些常識。這些,都可以通過getopt來實作,但有些實作起來可能會比較複雜。
1.區分option、parameter、argument、option argument和non-option parament
parameter和argument都表示參數,前者通常表示獨立性的參數,後者通常表示依賴于其它實體的參數。parameter的含義更廣,argument可以看作parameter的一種。
例如,定義函數時
function foo(x,y){CODE}
,函數的參數x和y稱為parameter。調用函數并傳遞參數時,
foo(arg1,arg2)
中的arg1和arg2都是依賴于函數的,稱為argument更合适,當然也可以稱為更廣泛的parameter。
再例如,一個指令行:
tar -zcf a.tar.gz /etc/pki
粗分的話,
-z
、
-c
-f
a.tar.gz
/etc/pki
都可以稱為parameter。細分的話:
- "-z -c -f"稱為選項,即option
- a.tar.gz是選項"-f"的選項參數(傳遞給選項的參數),依賴于選項,稱為argument更合适,更嚴格的稱呼是option argument
- /etc/pki既不屬于選項,也不屬于某個選項的參數,它稱為非選項類型的參數,對應的名稱為non-option parameter
本文要介紹的是getopt,是以隻考慮指令行參數的情況。
2.短選項和長選項以及它們的"潛規則"
Linux中絕大多數指令都提供了短選項和長選項。一般來說,短選項是隻使用一個"-"開頭,選項部分隻使用一個字元,長選項是使用兩個短橫線(即"--")開頭的。
例如"-a"是短選項,"--append"是長選項。
一般來說,選項的順序是無所謂的,但并非絕對如此,有時候某些選項必須放在前面,必須放在某些選項的前面、後面。
一般來說,短選項:
-
可以通過一個短橫線"-"将多個短選項連接配接在一起,但如果連在一起的短選項有參數的話,則必須作為串聯的最後一個字元。
例如"-avz"其實會被解析為"-a -v -z",
串聯了多個短選項,但"-f"選項有參數a.tar.gz,是以它必須作為串聯選項的最後一個字元。tar -zcf a.tar.gz
- 短選項的參數可以和選項名稱連在一起,也可以是用空白分隔。例如
和-n 3
是等價的,數值3都是"-n"選項的參數值。-n3
- 如果某個短選項的參數是可選的,那麼它的參數必須緊跟在選項名後面,不能使用空格分開。至于為什麼,見下面的第3項。
一般來說,長選項:
- 可以使用等号或空白連接配接兩種方式提供選項參數。例如
或--file=FILE
。--file FILE
- 如果某個長選項的參數是可選的,那麼它的參數必須使用"="連接配接。至于為什麼,見下面的第3項。
- 長選項一般可以縮寫,隻要不産生歧義即可。
例如,ls指令,以"a"開頭的長選項有3個。
$ ls --help | grep -- '--a'
-a, --all do not ignore entries starting with .
-A, --almost-all do not list implied . and ..
--author with -l, print the author of each file
如果想要指定
--almost-all
,可以縮寫為
--alm
;如果想要指定
--author
--au
。如果隻縮寫為"--a",bash将給出錯誤提示,長選項出現歧義:
$ ls --a
ls: option '--a' is ambiguous; possibilities: '--all' '--author' '--almost-all'
Try 'ls --help' for more information.
3.不帶參數的選項、可選參數的選項和帶參數的選項
有不同類型的指令行選項,這些選項可能不需要參數,也可能參數是可選的,也可能是強制要求參數的。
前面說了,如果某個選項的參數是可選的,那麼它的參數必須不能使用空格将參數和選項分開。如果使用空格分隔,則無法判斷它的下一個元素是該選項的參數還是非選項類型的參數。
例如,
-c
--config
選項的參數是可選的,要向這兩個選項提供參數,必須寫成
-cFILE
--config=FILE
,如果寫成
-c FILE
--config FILE
,那麼getopt無法判斷這個FILE是提供給選項的參數,還是非選項類型的參數。
一般來說,使用可選參數的情況非常少,至少我目前回憶不起來這樣的指令。
4.使用"--"将選項(及它們的選項參數)與非選項類型參數進行分隔
unix的指令行中,總是可以在非選項類型的參數之前加上"--",表示選項和選項參數到此為止,後面的都是非選項類型的參數。
例如:
seq -w -- 3
seq -w -- 1 3
分别表示3和"1 3"是seq的非選項類型參數,而"--"前面的一定是選項或選項參數。
5.指令行參數中的短橫線開頭的并不一定總是短選項,也可能是負數參數
例如seq指令:
seq -w -5 -1 5
其中-5和-1都是負數非選項類型的參數。
6.選項的依賴性和互斥性
有些指令的選項是有依賴性和互斥性的。比如某個選項要和另一個選項一起使用,某個選項不能和另一個選項一起使用。
例如
--manage --remove
,隻有在使用了
--manage
的前提下才能使用
--remove
,否則就應該報錯。
7.模式化(子產品化)類型的選項
很多unix指令都将選項進行子產品化設計。例如ip指令,address模式、route模式、link模式等等。
ip addr OPTIONS
ip route OPTIONS
ip link OPTIONS
ip neigh OPTIONS
8.其他特性的選項
有些指令還有比較個性化的選項,比如head指令,
-n NUM
選項,即可以指定為
-3
,也可以指定為
-n 3
-n3