Bash技巧:介紹 getopts 內置命令解析選項參數的用法

在 Linux bash shell 中,內置了一個 getopts 命令,能夠處理以 ‘-’ 開頭的選項參數。本篇文章經過多個實例詳解 getopts 命令的用法。shell

getopts 命令簡介

在 bash shell 上執行命令,經常會用到一些選項參數來指定不一樣的操做。例如 ls 命令的 -l-a 選項等。bash

咱們在編寫 shell 腳本時,也能夠自定義一些選項參數,並使用 bash 的 getopts 內置命令來解析選項參數。函數

查看 man bash 裏面對 getopts 內置命令的英文說明以下:ui

getopts optstring name [args]
getopts is used by shell procedures to parse positional parameters.
optstring contains the option characters to be recognized; if a character is followed by a colon, the option is expected to have an argument, which should be separated from it by white space.
The colon and question mark characters may not be used as option characters.

Each time it is invoked, getopts places the next option in the shell variable name, initializing name if it does not exist, and the index of the next argument to be processed into the variable OPTIND.
OPTIND is initialized to 1 each time the shell or a shell script is invoked.
When an option requires an argument, getopts places that argument into the variable OPTARG.
The shell does not reset OPTIND automatically; it must be manually reset between multiple calls to getopts within the same shell invocation if a new set of parameters is to be used.spa

When the end of options is encountered, getopts exits with a return value greater than zero.
OPTIND is set to the index of the first non-option argument, and name is set to ?.命令行

getopts normally parses the positional parameters, but if more arguments are given in args, getopts parses those instead.code

getopts can report errors in two ways. If the first character of optstring is a colon, silent error reporting is used.
In normal operation, diagnostic messages are printed when invalid options or missing option arguments are encountered.
If the variable OPTERR is set to 0, no error messages will be displayed, even if the first character of optstring is not a colon.orm

If an invalid option is seen, getopts places ? into name and, if not silent, prints an error message and unsets OPTARG.
If getopts is silent, the option character found is placed in OPTARG and no diagnostic message is printed.ip

If a required argument is not found, and getopts is not silent, a question mark (?) is placed in name, OPTARG is unset, and a diagnostic message is printed.
If getopts is silent, then a colon (:) is placed in name and OPTARG is set to the option character found.ci

注意getopts 是 bash 的內置命令。對於 bash 內置命令來講,不能用 man 命令查看它們的幫助說明。
要使用 help 命令查看。也能夠在 man bash 裏面搜索命令名稱查看相應的說明。

$ man getopts
No manual entry for getopts
$ help getopts
getopts: getopts optstring name [arg]
    Parse option arguments.

能夠看到,man getopts 提示找不到 getopts 命令的說明,而 help getopts 打印了它的說明。

另外,有一個 getopt 外部命令也能夠解析命令選項,名稱比 getopts 少了一個 s,用法也有所差別,不要把這兩個命令搞混了。

getopts optstring name [args]

基於 getopts optstring name [args] 這個命令格式,對 getopts 命令各個參數的含義說明以下。

optstring 參數指定支持的選項參數列表,每一個字符對應一個選項。
若是字符後面跟着冒號 :,那麼在輸入該選項時預期後面跟着一個參數,選項和參數之間用空格隔開。
不能使用冒號 : 和問號 ? 來做爲選項。

例如,一個有效的 optstring 參數值是 "hi:"。
那麼 -h 就是一個選項;-i 也是一個選項。
因爲在 i 後面跟着冒號 :,那麼輸入 -i 選項時還要提供一個參數,如 -i insert 等。
若是實際執行的時候,在 -i 後面不提供參數,getopts 命令會報錯。

注意:optstring 參數的選項列表不包含 - 字符,可是在實際輸入選項參數時,getopts 命令要求選項參數以 - 開頭,不然會報錯。
以上面例子來講,-h 是一個選項,可是 h 並非一個有效的選項。

name 參數用於保存解析後的選項名。
每調用一次 getopts 命令,它只解析一個選項,並把解析的值存入 name 變量中。
解析後的值不包含 - 字符。
例如解析 -h 選項後,name 變量的值是字符 h。
該變量的名稱不要求只能是 name 字符串,也能夠是其餘合法的變量名,例如 optarg 等等。

若是要解析多個選項時,須要在 while 或者 for 循環中屢次執行 getopts 命令,來逐個解析參數選項,直到解析完畢爲止。
解析完畢,getopts 命令會返回 false,從而退出循環。

若是提供的選項不在 optstring 指定的列表裏面,name 的值會被設成問號 ?
可是 getopts 命令仍是返回true,不會報錯。

[args] 是一個可選參數,用於指定選項參數的來源。
getopts 命令默認解析位置參數提供的參數,例如 $1$2、...、等等。
若是提供了 args 參數,那麼從 args 中解析選項參數,再也不從位置參數中解析。

也就是說,在 shell 腳本里面直接執行 getopts 命令,它默認解析的選項參數是執行腳本時提供的參數。
例若有一個 testgetopts.sh 腳本,那麼執行 ./testgetopts.sh -a -b 命令,getopts 會解析 -a-b 選項。

若是是在函數內執行 getopts 命令,它解析的選項參數是調用函數時提供的參數。
例若有一個 test_getopts 函數,該函數內調用 getopts 命令,那麼執行 test_getopts -a -b 語句,getopts 命令會解析 -a-b 選項。

若是提供了 args 參數,getopts 命令改爲解析 args 參數包含的選項。
例如執行 args="-a -b"; getopts "ab" opt $args 語句,getopts 命令解析的是 args 變量指定的 "-a -b" 字符串。

OPTARGgetopts 命令用到的一個全局變量,保存解析出來的帶冒號選項後面的參數值。
例如解析上面提到的 -i insert 選項,那麼 OPTARG 的值就是 insert

OPTINDgetopts 命令用到的一個全局變量,保存下一個待解析的參數index。
當啓動新的shell時,OPTIND 的默認值是 1,調用一次 getopts 命令,OPTIND 的值加 1。
若是帶冒號的選項後面提供了參數,OPTIND 的值會加 2。
getopts 命令解析完全部參數後,shell 不會自動重置 OPTIND 爲 1。

若是在同一個 shell 腳本里面要解析不一樣的選項參數,須要手動爲 OPTIND 賦值爲 1,不然會解析到不預期的選項。
後面會以一個 testgetopts.sh 腳本爲例進行說明。

getopts 命令的返回值

查看 man bash 裏面對 getopts 命令的返回值說明以下:

getopts returns true if an option, specified or unspecified, is found.
It returns false if the end of options is encountered or an error occurs.

能夠看到,即便提供不支持的選項,getopts 命令也是返回true

當解析完全部選項後,getopts 會返回 false,遇到錯誤時也會返回 false。
遇到錯誤的狀況有以下幾種:

  • 選項沒有以 - 開頭
  • 帶有冒號的選項要求後面提供一個參數,可是沒有提供該參數

testgetopts.sh 腳本

下面以一個 testgetopts.sh 腳本爲例來講明 getopts 命令的用法,其內容以下:

#!/bin/bash

function test_getopts_ab()
{
    local opt_ab
    while getopts "ab" opt_ab; do
        echo $FUNCNAME: $OPTIND: $opt_ab
    done
}

echo Before Call test_getopts_ab: OPTIND: $OPTIND ++
test_getopts_ab "-a" "-b"
echo After Call test_getopts_ab: OPTIND: $OPTIND --

while getopts "ef" opt_ef; do
    echo $OPTIND: $opt_ef
done

OPTIND=1
echo Reset OPTIND to: $OPTIND

number=6;
while getopts "s:g" opt_sg; do
    case $opt_sg in
        g) echo $number ;;
        s) number=$OPTARG ;;
        ?) echo "unknown option: $opt_sg" ;;
    esac
done

這個腳本先在 test_getopts_ab 函數中調用 getopts 命令,解析傳入的 "-a" "-b" 選項。
而後調用 getopts 命令解析執行腳本時傳入的命令行選項參數。
最後重置 OPTIND 的值,從新解析命令行選項參數。

getopts "ab" opt_ab 語句來講,"ab" 對應上面提到的 optstring 參數,它支持的選項就是 -a-b
opt_ab 對應上面提到的 name 變量名,保存解析獲得的選項,不包含 - 字符。

執行這個腳本,結果以下:

$ ./testgetopts.sh -s 7 -g -f
Before Call test_getopts_ab: OPTIND: 1 ++
test_getopts_ab: 2: a
test_getopts_ab: 3: b
After Call test_getopts_ab: OPTIND: 3 --
./testgetopts.sh: illegal option -- g
4: ?
5: f
Set OPTIND to: 1
7
./testgetopts.sh: illegal option -- f
unknown option: ?

能夠看到,test_getopts_ab 函數解析完選項參數後,在函數外打印 OPTIND 的值是 3。
以後再次調用 getopts 命令,OPTIND 值沒有從 1 開始,仍是從 3 開始取值,取到了傳給 testgetopts.sh 的第三個參數 -g,跳過了前面的 -s 7 兩個參數。
這並非預期的結果。正常來講,預期是從第一個選項參數開始解析。
因爲 getopts "ef" opt_ef 語句不支持 -g 選項,打印報錯信息,並把 opt_ef 賦值爲問號 ?

手動將 OPTIND 值重置爲 1 後,getopts "s:g" opt_sg 能夠從第一個選項參數開始解析。
先處理 -s 7 選項,getopts 把 7 賦值給 OPTARG,腳本里面再把 OPTARG 的值賦給 number 變量。
而後繼續處理 -g 選項,打印出 number 變量的值。
最後處理 -f 選項,該選項不支持,opt_sg 的值被設成問號 ?,打印 "unknown option" 的信息。
處理完全部選項後,getopts 返回 false,退出 while 循環。

錯誤判斷

getopts 命令處理完選項參數、或者遇到錯誤時,都會返回 false,不能經過判斷返回值來確認是否遇到了錯誤。

當在 while 或者 for 循環中調用 getopts 時,能夠經過 OPTIND 的值來判斷 getopts 是否遇到了錯誤。
若是 OPTIND 的值減去 1 後,不等於傳入的參數個數,那麼就是遇到了錯誤致使提早退出循環。

當 getopts 處理選項參數時,OPTIND 的值從 1 開始遞增。
處理全部參數後,OPTIND 指向最後一個參數,至關因而全部參數個數加 1。
因此 OPTIND 的值減去 1 就應該等於傳入的參數個數。

Bash 的 $# 表達式能夠獲取傳入的參數個數,若是這兩個值不相等,那麼 getopts 就沒有解析完選項參數,也就是遇到了錯誤致使提早退出循環。

假設有一個 test.sh 腳本,內容以下:

#!/bin/bash

while getopts "abcd" arg; do
    echo $OPTIND: $arg
done

echo OPTIND: $OPTIND. \$\#: $#
if [ "$(($OPTIND - 1))" != "$#" ]; then
    echo Error occurs.
fi

傳入不一樣的選項參數,執行該腳本的結果以下:

$ ./test.sh -a -b -c
2: a
3: b
4: c
OPTIND: 4. $#: 3
$ ./test.sh -a -b
2: a
3: b
OPTIND: 3. $#: 2
$ ./test.sh -a b
2: a
OPTIND: 2. $#: 2
Error occurs.

能夠看到,當正常遇到選項末尾時,OPTIND 變量的值是選項個數加 1。
當遇到錯誤時,OPTIND 變量的值不是選項個數加 1。
因此當 OPTIND 變量的值減去1,不等於 $# 時,就表示遇到了錯誤。

經過 source 屢次執行腳本對 OPTIND 的影響

經過 source 命令調用腳本是運行在當前 shell 下,因爲 shell 不會自動重置 OPTIND 的值,若是要調用的腳本使用了 getopts 命令解析選項參數,在每次調用 getopts 以前,必定要手動重置 OPTIND 爲 1,不然 OPTIND 的值不是從 1 開始遞增,會獲取到不預期的選項參數值。

假設有一個 test.sh 腳本,其內容以下:

#!/bin/bash

echo \$\#: $#, \$\@: $@, OPTIND: $OPTIND
getopts "abc" opt
echo $?, $opt

分別不使用 source 命令和使用 source 命令執行該腳本,結果以下:

$ ./test.sh -a
$#: 1, $@: -a, OPTIND: 1
0, a
$ ./test.sh -b
$#: 1, $@: -b, OPTIND: 1
0, b
$ source ./test.sh -a
$#: 1, $@: -a, OPTIND: 1
0, a
$ source ./test.sh -b
$#: 1, $@: -b, OPTIND: 2
1, ?

能夠看到,執行 ./test.sh -a 命令和 ./test.sh -b 命令的輸出結果都正常。
執行 source ./test.sh -a 命令的結果也是正常。
可是接着執行 source ./test.sh -b 命令,調用 getopts 以前,打印出 OPTIND 的值是 2,要獲取第二個選項參數。
因爲沒有提供第二個選項參數,獲取到的選項參數值是問號 ?,用 $? 獲取 getopts 命令的返回值是 1,執行報錯。

即,若是一個腳本使用了 getopts 命令,而該腳本又要用 source 命令來執行時,腳本須要手動設置 OPTIND 變量的值爲1,不然會遇到上面的異常。

當咱們本身寫了一個腳本,並在 .bashrc 文件中配置 alias 使用 source 命令來執行該腳本,例以下面的設置:

alias c='source quickcd.sh'

quickcd.sh 腳本使用了 getopts 命令,且沒有重置 OPTIND 的值,那麼屢次執行 c 這個命令,就會遇到上面描述的異常。

相關文章
相關標籤/搜索