前言:近期模仿磊哥的一個命令工具,遇到了shell里的getopt命令,它可以接受長短命令參數,原命令里只接了2個參數,我模仿的命令里需要接收3個參數,且都有長短寫法,模擬了半天始終無法獲取正確的參數,於是開始研究getopt命令。於是有了下面的轉載文章
引用自:https://blog.csdn.net/wanglz666/article/details/44565809
getopt簡介
以下主要翻譯自man getopt。
getopt是用來解析傳入shell的命令行參數的,它可以支持如 ‘rm -r’ 中’-r’形式的參數解析。
命令語法
先從一個較為明了的語法開始
getopt -o optstring -- parameters
這里,getopt所帶參數可以分成三個部分:
- -o和
--
選項是getopt命令自身的選項 - parameters是待解析的參數(如shell傳入的參數)
- -o選項后面的optstring用來指示解析parameters的方式
來看一個簡單的例子
getopt -o a: -- -a para_a
對應到命令格式
optstring | parameters |
---|---|
a: | -a para_a |
解析規則
getopt將根據optstring從左到右解析parameters。每個parameter將被解析為短選項(如上例中的-a)、長選項、選項參數(an argument to an option)及非選項參數(a non-option parameter)。
Tip: 這里的短選項僅指parameters中解析出的短選項,而非getopt自帶的短選項
'-o'
短選項
getopt的-o選項用來指示parameters中包含的短選項。parameters中的短選項由符號'-'
和緊連其后的一個字符構成。如上述簡單例子中-o后面的a:,指示parameters中可以包含一個形如-a 的短選項。
- a:中還有一個冒號,這指示-a有一個required argument;
- 如果是-a::,有兩個冒號,則指示-a有一個optional argument;
- 如果僅僅是-a,沒有冒號,則指示-a后面不需要任何argument,就好比很多命令中用來查看版本的-v選項,就是不用帶任何argument的。
如果該選項有一個required argument,則這個argument可以緊連着寫在選項之后,也可以隔一個空格。
如果該選項有一個optional argument,則該argument必須緊連着寫在選項之后。
看例子
- required argument的例子
getopt -o a: -- -a para_for_a getopt -o a: -- -apara_for_a
這兩個命令的輸出都是 -a 'para_for_a' --
para_for_a成功被解析為-a的選項參數,而--
后面的是非選項參數,在該例中,沒有非選項參數。
- optional argument的例子
getopt -o a:: -- -apara_for_a getopt -o a:: -- -a para_for_a
第一個命令輸出 -a 'para_for_a' --
第二個命令輸出 -a '' -- 'para_for_a'
可以看到,在optional argument的第二個命令中,如果para_for_a不緊跟在-a之后,則被當做了非選項參數。而-a選項需要的選項參數則被默認置空。
在-o后面也可以指定多個短選項,直接寫在一起就行了。
看例子
getopt -o ab::c: -- -a -bpara_for_b -c para_for_c
輸出結果-a -b 'para_for_b' -c 'para_for_c' --
該例中指定了一個無參數的-a選項,一個有optional argument的-b選項,以及一個有required argument的-c選項。
長選項
比如說-v是短選項,而--version
則是長選項。
getopt中可以使用-l來指定長選項。-l后也可以指定多個選項,多個選項之間以逗號分隔。長選項一般以--
接上長選項的名稱。
如例子
getopt -l a-long: -- --a-long=para_for_a-long //error
這里指定了a-long
這個長選項。
--a-long:
中還有一個冒號,這指示--a-long
有一個required argument;- 如果是
--a-long::
,有兩個冒號,則指示--a-long
有一個optional argument;- 如果僅僅是
--a-long
,沒有冒號,則指示--a-long
后面不需要任何argument,就好比很多命令中用來查看版本的--version
選項,就是不用帶任何argument的。
如果一個長選項有一個required argument,則其參數可以使用=
連接在其后,或者空一個空格連在氣候。
如果一個長選項有一個optional argument,則其參數必須使用=
連接在其后。
不過,上面的那個例子后面注釋了一個error。如果嘗試執行上面的那個例子的話,可以看到,並沒有出現預期中的結果。
原因是因為getopt對-o選項的處理。參看對-o選項的說明,有這樣一句
If this option is not found, the first parameter of getopt that does not start with a `-’ (and is not an option argument) is used as the short options string.
這句話的意思是說,如果getopt命令沒有發現-o選項,則會嘗試去找默認的short option string。
如果我們嘗試執行這樣的命令
getopt -l a-long: -- --a-long=para_for_a-long -a -l -o -n -g -p -r -f -z
其實也可以寫成
getopt -l a-long: -- --a-long=para_for_a-long -alongprfz
我們會得到以下的輸出
getopt: invalid option -- 'z'
-a -l -o -n -g -p -r -f --
具體是如何將alongprf這幾個字符解析成短選項的,我暫時沒有去深入了。
解決方法是明確指定-o為空,如下
getopt -o '' -l a-long: -- --a-long=para_for_a-long
此時,即可得到輸出
--a-long 'para_for_a-long' --
當初這個問題是參考的stackExchange上的Q&A
雙引號的作用
在接下來看更具體的例子之前,我們先來看看雙引號對getopt命令的作用。
getopt -o a: -- -a para_a getopt -o a: -- "-a para_a"
這兩個命令的輸出有細微的區別
第一個命令是-a 'para_a' --
第二個命令是-a ' para_a' --
第二個比第一個多了一個空格。
getopt -o a: -a para_a getopt -o a: "-a para_a"
這兩個命令區別明顯
第一個命令的輸出是-- 'para_a'
第二個命令的輸出是getopt: invalid option -- ' '
對於第一個命令的輸出,貌似跟預期的也不大一樣。這是因為-a是getopt本身自帶的一個選項,這樣para_a就被解析成了一個non-option parameter。
對於雙引號造成的區別,應該和shell的expansion有關,還不是很理解。但可以看到的是,雙引號使得空格保留下來了,作為了參數的一部分,使得getopt在處理時,將-a para_a
當做了一個整體。
Tip:這里沒有使用
--
man getopt中有說明 “The second part will start at the first non-option parameter that is not an option argument, or after the first occurrence of--
.”
也就是說,如果沒有--
的話,則getopt會將從第一個不是用來指定選項的參數(non-option parameter) 開始,將其后的內容解釋為getopt命令中的parameters部分。
shell腳本示例
上面了解了getopt的基本使用方法,這里展示一個在shell腳本中使用getopt的例子。
這個例子是getopt自帶的,在man getopt的EXAMPLES小節可以找到例子的路徑。
#!/bin/bash # A small example program for using the new getopt(1) program. # This program will only work with bash(1) # An similar program using the tcsh(1) script language can be found # as parse.tcsh # Example input and output (from the bash prompt): # ./parse.bash -a par1 'another arg' --c-long 'wow!*\?' -cmore -b " very long " # Option a # Option c, no argument # Option c, argument `more' # Option b, argument ` very long ' # Remaining arguments: # --> `par1' # --> `another arg' # --> `wow!*\?' # Note that we use `"$@"' to let each command-line parameter expand to a # separate word. The quotes around `$@' are essential! # We need TEMP as the `eval set --' would nuke the return value of getopt. # 上面這句話的意思是,我們需要TEMP變量是因為`eval set --'命令會覆蓋掉getopt命令的返回碼, # 這樣我們就無法判斷getopt命令的執行成功與否了(居然為此糾結了許久。。。) # 下面一句中 $@ 是shell腳本傳入的參數 TEMP=`getopt -o ab:c:: --long a-long,b-long:,c-long:: \ -n 'example.bash' -- "$@"` if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi # Note the quotes around `$TEMP': they are essential! eval set -- "$TEMP" while true ; do case "$1" in -a|--a-long) echo "Option a" ; shift ;; -b|--b-long) echo "Option b, argument \`$2'" ; shift 2 ;; -c|--c-long) # c has an optional argument. As we are in quoted mode, # an empty parameter will be generated if its optional # argument is not found. case "$2" in "") echo "Option c, no argument"; shift 2 ;; *) echo "Option c, argument \`$2'" ; shift 2 ;; esac ;; --) shift ; break ;; *) echo "Internal error!" ; exit 1 ;; esac done echo "Remaining arguments:" for arg do echo '--> '"\`$arg'" ; done
這里對eval set和shift進行說明。
shift命令
較簡單,即是將位置參數進行左移。
位置參數是指shell中
$0
,$1
等參數。
set命令
set --
用來設置位置參數
我們結合set和shift來寫個例子
set -- a b echo "before shift 1," '$1' "is $1" echo "before shift 1," '$2' "is $2" shift 1 echo "after shift 1," '$1' "is $1" echo "after shift 1," '$2' "is $2"
依次執行以上命令(或寫在shell腳本中執行),得到的輸出如下
before shift 1, $1 is a
before shift 1, $2 is b
after shift 1, $1 is b
after shift 1, $2 is
eval
eval是對其后的命令再次進行shell解析,比如參數替換什么的。
在man getopt的DESCRIPTION一節的最后一段,有這么一句
Traditional implementations of getopt(1) are unable to cope with whitespace and other (shell-specific) special characters in arguments and non-option parameters. To solve this problem, this implementation can generate quoted output which must once again be interpreted by the shell (usually by using the eval command).
這意思就是在示例中eval命令是用來保留一些特殊字符,如空格的。
感覺這里eval的作用和雙引號有點像,然后在代碼的注釋中,還有兩處essential。分別是說$@
的雙引號essential,以及eval中$TEMP
的雙引號essential。這里大約都與特殊符號有關吧。
更進一步的解釋可以參考Bash: Preserving Whitespace Using set and eval,這篇文章最后一點也沒看明白。。。
實際運行
使用例子中提供的命令./parse.bash -a par1 'another arg' --c-long 'wow!*\?' -cmore -b " very long "
,這里面包含了很多特殊符號。
- 兩處引號均存在的輸出
Option a Option c, no argument Option c, argument `more' Option b, argument ` very long ' Remaining arguments: --> `par1' --> `another arg' --> `wow!*\?'
- 僅去除
$@
的引號的輸出
Option a Option c, no argument Option c, argument `more' Option b, argument `very' Remaining arguments: --> `par1' --> `another' --> `arg' --> `wow!*\?' --> `long'
- 僅去除eval中
$TEMP
的引號的輸出
Option a Option c, no argument Option c, argument `more' Option b, argument ` very long ' Remaining arguments: --> `par1' --> `another arg' --> `wow!*\?'
- 兩處引號均去除的輸出
Option a Option c, no argument Option c, argument `more' Option b, argument `very' Remaining arguments: --> `par1' --> `another' --> `arg' --> `wow!*\?' --> `long'
從上面的結果中看到,如果$@
的引號缺失了,會使得'another arg'
和" very long "
的空格丟失;而$TEMP
引號是否缺失,似乎並無影響。
不是很明白。。。
最后
感覺getopt就是按照指定規則對參數進行重排序的過程。
從getopt自帶的示例中也可以看到,重排序后的結果被set命令設置為位置參數,再由用戶自行處理。
發現想寫一個東西,往往牽扯到好多細節。
這篇文章寫到最后,依然沒弄清eval和雙引號的作用