我正在解析選項,getopts
但也想處理長選項。
print-args ()
{
title="$1" ; shift
printf "\n%s\n" "${title}: \$@:"
for arg in "$@"; do
(( i = i + 1 ))
printf "%s |%s|\n" "${i}." "$arg"
done
}
getopts_test ()
{
aggr=()
for arg in "$@"; do
case $arg in
("--colour"|"--color") aggr+=( "-c" ) ;;
("--colour="*|"--color="*) aggr+=( "-c" "${arg#*=}" ) ;;
(*) aggr+=( "$arg" ) ;;
esac
done
print-args "print" "$@"
eval set -- "${aggr[@]}"
print-args "eval" "$@"
set -- "${aggr[@]}"
print-args "set" "$@"
local OPTIND OPTARG
local shortopts="C:"
while getopts "$shortopts" arg; do
case $arg in
("c") context="$OPTARG" ;;
(*) break ;;
esac
done
shift $(( OPTIND - 1 ))
}
但我想知道使用是否set -- "${aggr[@]}"
正確。
或以下(使用eval
)更合適?
eval set -- "${aggr[@]}"
我進行瞭如下所示的測試。使用 eval 時,字串「Gunga Din」被拆分,而使用 時set -- "${aggr[@]}"
,它被正確解析為單一字串。
getopts_test -f -g 130 --colour="170 20" "Gunga Din"
print: $@:
1. |-f|
2. |-g|
3. |130|
4. |--colour=170 20|
5. |Gunga Din|
eval: $@:
1. |-f|
2. |-g|
3. |130|
4. |-c|
5. |170|
6. |20|
7. |Gunga|
8. |Din|
set: $@:
1. |-f|
2. |-g|
3. |130|
4. |-c|
5. |170 20|
6. |Gunga Din|
然後我運行了另一個使用非 GNU 的函數getopt
。
getopt_test ()
{
shortopts="Vuhv::H::w::e::n::l::C:"
shortopts="${shortopts}bgcrmo"
longopts="version,usage,help,verbosity::"
longopts="${longopts},heading::,warning::,error::"
longopts="${longopts},blu,grn,cyn,red,mgn,org"
opts=$( getopt -o "$shortopts" -l "$longopts" -n "${0##*/}" -- "$@" )
print-args "\$@:" "$@"
print-args "opts:" "$opts"
set -- "$opts"
print-args "set -- \"$opts\"" "$@"
eval set -- "$opts"
print-args "eval set -- \"$opts\"" "$@"
}
這導致了以下結果
getopt_test --warning=3 "foo'bar" "Gunga Din"
$@:
1. |--warning=3|
2. |foo'bar|
3. |Gunga Din|
opts:
1. | --warning '3' -- 'foo'\''bar' 'Gunga Din'|
set -- "$opts"
1. | --warning '3' -- 'foo'\''bar' 'Gunga Din'|
eval set -- "$opts"
1. |--warning|
2. |3|
3. |--|
4. |foo'bar|
5. |Gunga Din|
如圖所示,getopt 的結果是一個帶有重新排列的位置參數的條目。這表示需要將字串eval set -- "$opts"
中的位置參數拆分opts
為五個條目以進行選項解析和處理。
答案1
其想法是預處理參數並將每個參數更改--context
為可以處理的參數-C
?getopts
我想這會起作用,但請注意,GNU 風格的長選項也可以採用 format 的參數--context=foobar
,而您的構造在這裡不支援這一點。使用者需要知道這裡的這個特定工具需要--context
foobar
兩個不同的參數。或者您需要使預處理更加複雜。
您可能還想檢查以 開頭的所有參數--
,否則,例如,輸入錯誤將按原樣進行,並且您會收到有關未知選項的投訴--cotnext
。getopts
(或更糟的是,將啟用錯誤的選項。)
但我想知道使用是否
set -- "${aggr[@]}"
正確。或以下(使用 eval)更合適?
set -- "${aggr[@]}"
將陣列的元素擴展為不同的單字,然後將這些單字分配給位置參數。每個數組元素將恰好成為一個位置參數,無需更改。
eval set -- "${aggr[@]}"
將展開數組的所有元素,然後用空格將它們連接在一起,在前面加上set --
並將結果作為 shell 命令進行計算。也就是說,如果您有數組元素abc def
, $(date >&2)
, ghi'jkl
,則命令將是
set -- abc def $(date >&2) ghi'jkl
它最終會以abc
和def
作為兩個不同的參數,並且會將日期列印到 stderr,除了單獨的單引號會導致語法錯誤。
eval
如果您有一些旨在產生為 shell 輸入引用的輸出的東西,則使用它是合適的。
如果您使用的是 Linux(並且不關心可攜性),您可以執行 roaima 在評論中建議的操作,並使用 util-linux 版本getopt
(不含s
)。它也支援長選項,有答案顯示如何使用它getopt、getopts 或手動解析 - 當我想同時支援短選項和長選項時使用什麼?並在這個答案並且我的回答在這裡。
順便說一句,這樣getopt
,你會use eval
,因為作為命令,它僅限於生成單個字串作為輸出,而不是像數組那樣的列表,因此它使用 shell 引用來解決該問題。
答案2
您可以--foo
使用內建函數解析 -style 長選項,getopts
方法是將參數新增-
為短選項,並將參數傳遞給 optstring,然後從 中擷取實際的長選項$OPTARG
。簡單的例子:
while getopts :sc:-: o; do
case $o in
:) echo >&2 "option -$OPTARG needs an argument"; continue;;
'?') echo >&2 "unknown option -$OPTARG"; continue;;
-) o=${OPTARG%%=*}; OPTARG=${OPTARG#"$o"}; OPTARG=${OPTARG#=};;
esac
echo "OPT $o=$OPTARG"
done
shift "$((OPTIND - 1))"
echo "ARGS $*"
然後您可以將其用作script -c foo
或script --context=foo
。
如果您還希望像短選項一樣驗證長選項,並且還接受縮寫形式,那麼您需要更複雜的東西。過度設計這樣一個糟糕的 shell 腳本並不明智,但如果你想要一個例子,這裡是:
short_opts=sc:
long_opts=silent/ch/context:/check/co # those who take an arg END with :
# override via command line for testing purposes
# if [ "$#" -ge 2 ]; then
# short_opts=$1; long_opts=$2; shift 2
# fi
while getopts ":$short_opts-:" o; do
case $o in
:) echo >&2 "option -$OPTARG needs an argument" ;continue;;
'?') echo >&2 "bad option -$OPTARG" ;continue;;
-) o=${OPTARG%%=*}; OPTARG=${OPTARG#"$o"}; lo=/$long_opts/
case $lo in
*"/$o"[!/:]*"/$o"[!/:]*) echo >&2 "ambiguous option --$o"; continue;;
*"/$o"[:/]*) ;;
*) o=$o${lo#*"/$o"}; o=${o%%[/:]*} ;;
esac
case $lo in
*"/$o/"*) OPTARG= ;;
*"/$o:/"*)
case $OPTARG in
'='*) OPTARG=${OPTARG#=};;
*) eval "OPTARG=\$$OPTIND"
if [ "$OPTIND" -le "$#" ] && [ "$OPTARG" != -- ]; then
OPTIND=$((OPTIND + 1))
else
echo >&2 "option --$o needs an argument"; continue
fi;;
esac;;
*) echo >&2 "unknown option --$o"; continue;;
esac
esac
echo "OPT $o=$OPTARG"
done
shift "$((OPTIND - 1))"
echo "ARGS $*"
然後
$ ./script --context=33
OPT context=33
$ ./script --con=33
OPT context=33
$ ./script --co
OPT co=
$ ./script --context
option --context needs an argument