man 1 getopt翻譯:http://www.javashuo.com/article/p-majexvln-ca.htmlhtml
寫shell腳本的時候,經過while、case、shift來設計腳本的命令行選項是一件比較麻煩的事,由於Unix命令行的選項和參數自由度很高,支持短選項和長選項,參數多是可選的,選項順序多是無所謂的,等等。mysql
bash下的getopt命令能夠解析命令行的選項和參數,將散亂、自由的命令行選項和參數進行改造,獲得一個完整的、規範化的參數列表,這樣再使用while、case和shift進行處理就簡單的太多了。sql
getopt有不一樣的版本,本文介紹的是它的加強版(enhanced),相比傳統的getopt(也稱爲兼容版本的getopt),它提供了引號保護的能力。另外,除了不一樣版本的getopt,bash還有一個內置命令getopts(注意,有個尾隨的字符s),也用來解析命令行選項,但只能解析短選項。shell
要驗證安裝的getopt是加強版的仍是傳統版的,使用getopt -T
判斷便可。若是它什麼都不輸出,則是加強版,此時它的退出狀態碼爲4。若是輸出"--",則是傳統版的getopt,此時它的退出狀態碼爲0。若是想在腳本中進行版本檢查,能夠參考以下代碼:bash
getopt -T &>/dev/null;[ $? -ne 4 ] && { echo "not enhanced version";exit 1; }
在學習getopt如何使用以前,必須先知道命令行的一些常識。這些,均可以經過getopt來實現,但有些實現起來可能會比較複雜。app
1.區分option、parameter、argument、option argument和non-option parameter模塊化
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。細分的話:
本文要介紹的是getopt,因此只考慮命令行參數的狀況。
2.短選項和長選項以及它們的"潛規則"
Linux中絕大多數命令都提供了短選項和長選項。通常來講,短選項是隻使用一個"-"開頭,選項部分只使用一個字符,長選項是使用兩個短橫線(即"--")開頭的。
例如"-a"是短選項,"--append"是長選項。
通常來講,選項的順序是無所謂的,但並不是絕對如此,有時候某些選項必須放在前面,必須放在某些選項的前面、後面。
通常來講,短選項:
能夠經過一個短橫線"-"將多個短選項鍊接在一塊兒,但若是連在一塊兒的短選項有參數的話,則必須做爲串聯的最後一個字符。
例如"-avz"其實會被解析爲"-a -v -z",tar -zcf a.tar.gz
串聯了多個短選項,但"-f"選項有參數a.tar.gz,因此它必須做爲串聯選項的最後一個字符。
-n 3
和-n3
是等價的,數值3都是"-n"選項的參數值。若是某個短選項的參數是可選的,那麼它的參數必須緊跟在選項名後面,不能使用空格分開。至於爲何,見下面的第3項。
通常來講,長選項:
--file=FILE
或--file FILE
。例如,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
,那麼命令行將沒法判斷這個FILE是提供給選項的參數,仍是非選項類型的參數。
通常來講,使用可選參數的狀況很是少,至少我目前回憶不起來這樣的命令(mysql -p選項是一個)。
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
。
再好比有的命令支持邏輯運算,例如find命令的-a、-o選項。
bash的getopt命令常常用在shell腳本內部或函數內部,用來解析腳本執行或函數執行時傳遞的選項、參數。
下面都以命令行爲例解釋getopt是如何解析參數的,但用來解析函數參數是同樣的。
下面這個是最經常使用的getopt解析方式(有這個命令就夠了)。若是要了解getopt更完整的語法,見man getopt。
getopt -o SHORT_OPTIONS -l LONG_OPTIONS -n "$0" -- "$@"
其中:
-o SHORT_OPTIONS
--options SHORT_OPTIONS
getopt經過"-o"選項收集命令行傳遞的短選項和它們對應的參數。關於SHORT_OPTIONS的格式見下一小節。
-l LONG_OPTIONS
--longoptions LONG_OPTIONS
getopt經過"-l"選項收集命令行傳遞的長選項和它們對應的參數。可能從別人的腳本中常常看到"--long",是等價的,前文已經解釋過,長選項只要不產生歧義,是能夠進行縮寫的。關於LONG_OPTIONS的格式見下一小節。
-n NAME
getopt在解析命令行時,若是解析出錯(例如要求給參數的選項沒帶參數,使用了沒法解析的選項等)將會報告錯誤信息,getopt將使用該NAME做爲報錯的腳本名稱。
-- "$@"
其中--
表示getopt命令自身的選項到此結束,後面的元素都是要被getopt解析的命令行參數。這裏使用"$@"
,表示全部的命令行參數。注意,不能省略雙引號。
getopt使用"-o"或"-l"解析短、長選項和參數時,將會對每一個解析到的選項、參數進行輸出,而後不斷放進一個字符串中。這個字符串的內容就是完整的、規範化的選項和參數。
getopt使用"-o"選項解析短選項時:
getopt -o ab:c::
中,將解析爲-a -b arg_b -c [arg_c]
,arg_b是-b選項必須的,arg_c是-c選項可選的參數,"-a"選項無需參數getopt使用"-l"選項解析長選項時:
getopt -l add:,remove::,show
中,將解析爲--add arg_add --remove [arg_rem] --show
,其中arg_add是--add
選項必須的,--remove
選項的參數arg_rem是可選的,--show
無需參數若是解析的是帶參數的選項,則getopt生成的字符串中,會將選項的參數值做爲該選項的下一個參數。若是解析的是可選參數的選項,若是爲該選項設置了參數,則會將這個參數放在選項的下一個參數位置,若是沒有爲該選項設置參數,則會生成一個用引號包圍的空字符串做爲選項的下一個參數。
getopt解析完選項和選項的參數後,將解析非選項類型的參數(non-option parameter)。getopt爲了讓非選項類型的參數和選項、選項參數區分開,將在解析第一個非選項類型參數時加上一個"--"到字符串中,表示選項和選項參數到此結束,而後將全部的非選項類型參數放在這個"--"參數以後。
默認狀況下,該增強版本的getopt會將全部參數值(包括選項參數、非選項類型的參數)使用引號進行包圍,以便保護空白字符和特殊字符。若是是兼容版本的getopt,則不會用引號保護,因此會破壞參數解析。
看後面的示例就很容易理解了。
例如在腳本test.sh中,下面的getopt的結果保存到變量parameters中,而後輸出getopt解析完成後獲得的完整參數列表。
#!/usr/bin/env bash parameters=`getopt -o ab:c:: --long add:,remove::,show -n "$0" -- "$@"` echo "$parameters"
若是還不知道這裏的-o
和--long
解析了什麼東西,請回頭仔細再看一遍。
執行這個腳本,並給這個腳本傳遞一些選項和參數,這些腳本參數將被收集到$@
,而後被getopt解析。
$ ./test.sh -a non-op_arg1 -b b_short_arg non-op_arg2 --rem --add /path --show -c non-op_arg3 -a -b 'b_short_arg' --remove '' --add '/path' --show -c '' -- 'non-op_arg1' 'non-op_arg2' 'non-op_arg3'
首先能夠看出,傳遞給腳本的參數都是無序的:
--rem
:是--remove的縮寫形式,它的參數是可選的,但沒有爲它傳遞參數--add
:並設置了該選項的參數/path--show
:沒有任何參數-a
:它是無需參數的選項,因此它後面的non-op_arg1是一個非選項類型的參數-b
:它是必須帶參數的選項,因此b_short_arg是它的參數-c
:它的參數是可選的,這裏沒有給它提供參數(前面解釋過,要給參數可選的選項提供參數,短選項時,參數和選項名稱必須連在一塊兒)。從getopt的輸出結果中,能夠看出:
getopt解析獲得了完整、規範化的結果,固然要拿來應用。例如直接傳遞個函數,或者根據while、case、shift將選項、參數進行分割單獨保存。
若是要進行分割,因爲getopt的解析結果一般保存在一個變量中,要解析這個結果字符串,須要使用eval函數將變量的內容進行還原,通常來講會將其設置爲一個位置參數(由於shift只能操做位置變量)。
通常來講,整個處理流程是這樣的:
parameters=$(getopt -o SHORT_OPTIONS -l LONG_OPTIONS -n "$0" -- "$@") [ $? != 0 ] && exit 1 eval set -- "$parameters" # 將$parameters設置爲位置參數 while true ; do # 循環解析位置參數 case "$1" in -a|--longa) ...;shift ;; # 不帶參數的選項-a或--longa -b|--longb) ...;shift 2;; # 帶參數的選項-b或--longb -c|--longc) # 參數可選的選項-c或--longc case "$2" in "")...;shift 2;; # 沒有給可選參數 *) ...;shift 2;; # 給了可選參數 esac;; --) ...; break ;; # 開始解析非選項類型的參數,break後,它們都保留在$@中 *) echo "wrong";exit 1;; esac done
須要注意,getopt解析既能夠放在腳本中解析命令行參數,也能夠放在某個函數中解析函數參數。
getopt提供了兩種掃描模式,只要在getopt的短選項前加上加號或負號,就能指定兩種掃描模式,即getopt -o [+-]SHORT_OPTS
。
+
掃描模式:只要解析完選項、選項參數,解析到第一個非選項類型的參數後,就會中止解析,它會將全部沒有解析的內容都看成非選項類型參數。因此這種狀況下,非選項類型的參數都必須放在尾部,而不能放在某個待解析選項的前面。這種模式在區別負數和短選項時,很是有用。-
掃描模式:會按照原始位置參數解析,並保留原始位置。這種模式通常用不上,由於破壞了getopt的優點:讓選項完整、規範化。例如,對於命令行參數-w -s -5 3 -2
,要將-5識別爲-s的參數,3和-2爲非選項類型的參數,則:
$ set -- -w -s -5 3 -2 # 設置位置參數 $ getopt -o +s:w -n "$0" -- "$@" -w -s '-5' -- '3' '-2' # 解析結果
注意,上面的-5是被解析成了-s的參數,而不是選項或非選項類型的參數,由於-s選項必需要指定一個參數。
上面的3必須不能是負數,由於getopt必須先掃描到一個正常的非選項型參數,才能將它後面的全部負數都看成非選項型參數。至於如何將-w -s -5 -3 -2
中的-3和-2都解析爲非選項型參數,目前我也不知道。
使用-
掃描模式:
$ set -- 3 -w 4 -s -5 a 3 $ getopt -o -s:w -n "$0" -- "$@" '3' -w '4' -s '-5' 'a' '3' -- # 解析結果
能夠看到,上面的全部參數位置都是保持原樣的,且將分隔符號"--"補在了最尾部。
在前面命令行選項的那些事中介紹了幾種有"個性"的選項功能,包括:
-w -5 -3 5
,其中-5和-3不是短選項,而是負數參數script_name MODE OPTIONS
的MODE部分,能夠是manage模式(--manage,-m),也可使用add模式(--add,-a)head -n 3
能夠替換爲head -3
這裏介紹下用getopt解析參數後實現它們的思路。
在getopt解析完成後,假設返回結果保存到了$parameters
變量中。
1.選項依賴性
這個其實很好實現,只需使用grep對$parameters
變量進行篩選一下便可。
例如實現依賴性,只需:
{ echo "$parameters" | grep -E '\-\-add|\-a ' | grep -E '\-\-manage|\-m '; } &>/dev/null [ $? -ne 0 ] && exit
2.選項互斥性
要實現互斥性,只需:
or_op=`echo "$parameters" | grep -Eo '\-\-add|\-a | \-\-remove|\-r ' | wc -l` [ "$or_op" = "2" ] && exit
3.識別負數參數
前面解釋過,getopt提供了兩種掃描模式,只要使用+
掃描模式,就能輕鬆區別負數參數和短選項。
4.模式化選項
通常來講,模式化選項都是命令行的第一個參數。因此,只需將$parameter
中"--"後面的第一個非選項類型的參數提取出來,就是所謂的模式了。固然,還得對這個參數進行一些判斷,避免它不是模式參數。
例如,要提供addr、show、route三種模式,那麼其它的非選項類型參數值都不該該是模式參數。
eval set -- "$parameters" while true ; do case "$1" in ... --) shift [ "$x" = "addr" -o "$x" = "route" -o "$x" = "show" ] && MODE=$1 shift break ;; *) echo "wrong";exit 1;; esac done
5.選項參數替代選項
就以-n3
和-3
爲例,它的通用格式是-n NUM
和-NUM
。這個並很差實現,我能想到的方法是將這個-NUM
先從$@
中篩選出來,而後賦值。
NUM=`echo "$@" | grep -Eo "\-[0-9]+"` ARGS=`echo "$@" | sed -nr 's!(.*)-[0-9]+(.*)!\1\2!'p` eval set -- "$ARGS"
這裏提供一個和seq命令功能相同的腳本seq.sh,而後設計這個腳本的選項。
先看一下seq命令的各個選項說明:
seq [OPTION]... LAST # 語法1 seq [OPTION]... FIRST LAST # 語法2 seq [OPTION]... FIRST INCREMENT LAST # 語法3 選項: -s, --separator=STRING 使用指定的STRING分隔各數值,默認值爲"\n"u -w, --equal-width 使用0填充在前綴使全部數值長度相同 --help 顯示幫助信息並退出 --version 輸出版本信息並退出
如下是腳本內容:和seq相比,只有兩個問題:第一個起點數值FIRST不能爲負數;不支持小數功能。其它功能徹底相同
#!/usr/bin/env bash ########################################################### # author : 駿馬金龍 # # blog : http://www.cnblogs.com/f-ck-need-u/ # ########################################################### usage(){ cat <<'EOF' Usage: $0 [OPTION]... LAST or: $0 [OPTION]... FIRST LAST or: $0 [OPTION]... FIRST INCREMENT LAST EOF } # getopt的版本是加強版嗎 getopt -T &>/dev/null;[ $? -ne 4 ] && { echo "not enhanced version";exit 1; } # 參數解析 parameters=`getopt -o +s:w --long separator:,equal-width,help,version -n "$0" -- "$@"` [ $? -ne 0 ] && { echo "Try '$0 --help' for more information."; exit 1; } eval set -- "$parameters" while true;do case "$1" in -w|--equal-width) ZERO_PAD="true"; shift ;; -s|--separator) SEPARATOR=$2; shift 2 ;; --version) echo "$0 version V1.0"; exit ;; --help) usage;exit ;; --) shift FIRST=$1 INCREMENT=$2 LAST=$3 break ;; *) usage;exit 1;; esac done # 用於生成序列數 function seq_func(){ # 是否要使用printf填充0位? [ "x$1" = "xtrue" ] && zero_pad="true" && shift # 設置first、step、last if [ $# -eq 1 ];then first=1 step=1 last=$1 elif [ $# -eq 2 ];then first=$1 step=1 last=$2 elif [ $# -eq 3 ]; then first=$1 step=$2 last=$3 else echo "$FUNCNAME: ARGS wrong..." exit 1 fi # 最後一個要輸出的元素及其長度,決定要填充多少個0 last_output=$[ last - ( last-first ) % step ] zero_pad_len=`[ ${#last_output} -gt ${#first} ] && echo ${#last_output} || echo ${#first}` # 生成序列數 if [ "x$zero_pad" = "xtrue" ];then # 填充0 if [ $step -gt 0 ];then # 遞增,填充0 for((i=$first;i<=$last;i+=$step)){ [ $last_output -eq $i ] && { printf "%0${zero_pad_len}i\n" "$i";return; } printf "%0${zero_pad_len}i " $i } else # 遞減,填充0 for((i=$first;i>=$last;i+=$step)){ [ $last_output -eq $i ] && { printf "%0${zero_pad_len}i\n" "$i";return; } printf "%0${zero_pad_len}i " $i } fi else # 不填充0 if [ $step -gt 0 ];then # 遞增,不填充0 for((i=$first;i<=$last;i+=$step)){ [ $last_output -eq $i ] && { printf "%i\n" "$i";return; } printf "%i " $i } else # 遞減,不填充0 for((i=$first;i>=$last;i+=$step)){ [ $last_output -eq $i ] && { printf "%i\n" "$i";return; } printf "%i " $i } fi fi } # 指定輸出分隔符 : ${SEPARATOR="\n"} # 輸出結果 seq_func $ZERO_PAD $FIRST $INCREMENT $LAST | tr " " "$SEPARATOR"
上面解析選項的腳本缺陷在於沒法解析FIRST爲負數的狀況,例如./seq.sh -w -5 3
將報錯。但能夠寫爲標準的./seq.sh -w -- -5 -3
語法。