Estou analisando opções, getopts
mas também gostaria de lidar com opções longas.
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 ))
}
Mas me pergunto se o uso de set -- "${aggr[@]}"
está correto.
Ou o seguinte (usando eval
) é mais apropriado?
eval set -- "${aggr[@]}"
Eu realizei um teste mostrado abaixo. Com eval, a string "Gunga Din" é dividida, enquanto com set -- "${aggr[@]}"
, ela está sendo analisada corretamente como uma única string.
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|
Então executei outra função que usa o não-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\"" "$@"
}
Isto resultou no seguinte
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|
Conforme mostrado, o resultado de getopt é uma entrada única com argumentos posicionais reorganizados. Isso mostra a necessidade de eval set -- "$opts"
dividir os argumentos posicionais na opts
string em cinco entradas para análise e processamento de opções.
Responder1
A ideia é pré-processar os argumentos e alterar cada um --context
para -C
que getopts
possa então processar? Suponho que funcionaria, mas observe que as opções longas no estilo GNU também podem receber argumentos no format --context=foobar
, e sua construção aqui não suporta isso. O usuário precisaria saber que esta ferramenta específica aqui requer --context
foobar
dois argumentos distintos. Ou você precisaria tornar o pré-processamento mais complexo.
Você também pode querer verificar todos os argumentos que começam com --
, caso contrário, por exemplo, um erro digitado --cotnext
iria para getopts
o estado em que se encontra e você receberia reclamações sobre opções desconhecidas. (Ou pior, opções erradas seriam habilitadas.)
Mas me pergunto se o uso de
set -- "${aggr[@]}"
está correto.Ou o seguinte (usando eval) é mais apropriado?
set -- "${aggr[@]}"
expande os elementos da matriz para palavras distintas e, em seguida, atribui essas palavras aos parâmetros posicionais. Cada elemento do array se tornará exatamente um parâmetro posicional, sem alterações.
eval set -- "${aggr[@]}"
expandiria todos os elementos da matriz, depois os uniria com espaços, acrescentaria set --
e avaliaria o resultado como um comando shell. Ou seja, se você tiver os elementos do array abc def
, $(date >&2)
, ghi'jkl
, o comando seria
set -- abc def $(date >&2) ghi'jkl
que terminaria com abc
e def
como dois parâmetros distintos e imprimiria a data em stderr, exceto que a aspa simples causaria um erro de sintaxe.
Usar eval
seria apropriado se você tiver algo projetado para produzir uma saída citada para entrada do shell.
Se você estiver no Linux (e não se importa com portabilidade), você pode fazer o que roaima sugeriu nos comentários e usar a versão util-linux getopt
(sem o s
). Ele também suporta opções longas, há respostas mostrando como usá-lo emgetopt, getopts ou análise manual - o que usar quando desejo oferecer suporte a opções curtas e longas?e emesta resposta SOe tambémminha resposta aqui.
Aliás, com isso getopt
, vocêseriause eval
, já que como um comando, ele se limita a produzir apenas uma única string como saída, e não uma lista como uma matriz, por isso usa aspas do shell para solucionar o problema.
Responder2
Você pode analisar --foo
opções longas no estilo - com o getopts
builtin adicionando -
como uma opção curta, levando um argumento para a optstring e recuperando a opção longa real de $OPTARG
. Exemplo simples:
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 $*"
que você pode usar como script -c foo
ou script --context=foo
.
Se você também deseja que as opções longas sejam validadas assim como as curtas, e também aceite formas abreviadas, você precisa de algo mais complexo. Não há muita sabedoria em projetar demais um script de shell ruim como esse, mas se você quiser um exemplo, aqui está:
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 $*"
então
$ ./script --context=33
OPT context=33
$ ./script --con=33
OPT context=33
$ ./script --co
OPT co=
$ ./script --context
option --context needs an argument