我正在解析选项,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