shell命令getopt簡介


  前言:近期模仿磊哥的一個命令工具,遇到了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和雙引號的作用

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM