Como usar getopt na linha de comando do bash apenas com opções longas?

Como usar getopt na linha de comando do bash apenas com opções longas?

Existe um getoptcomando na linha de comando do bash. getoptpode ser usado com opções curtas (como getopt -o axby "$@") e pode ser usado com opções curtas e longas (como getopt -o axby -l long-key -- "$@"), mas agora precisoapenasopções longas (ou seja, opções curtas não existem), porém o comando getopt -l long-key -- "$@"não analisa --long-keya opção corretamente. Então, como posso usar getopto comando comapenasopções longas? Ou é impossível ou é apenas um bug do getoptcomando?

Responder1

getoptestá perfeitamente bem em não ter opções curtas. Mas você precisa dizer que não tem opções curtas. É uma peculiaridade na sintaxe - do manual:

Se no -oou --optionsoption for encontrado na primeira parte, o primeiro parâmetro da segunda parte será usado como a sequência curta de opções.

É isso que está acontecendo no seu teste: getopt -l long-key -- --long-key footrata --long-keycomo lista de opções -egklnoye foocomo único argumento. Usar

getopt -o '' -l long-key -- "$@"

por exemplo

$ getopt -l long-key -o '' -- --long-key foo
 --long-key -- 'foo'
$ getopt -l long-key -o '' -- --long-key --not-recognized -n foo
getopt: unrecognized option '--not-recognized'
getopt: invalid option -- 'n'
 --long-key -- 'foo'

Responder2

Não sei, getoptmas o getoptsbuiltin pode ser usado para lidar apenas com opções longas como esta:

while getopts :-: o
do  case "$o$OPTARG" in
(-longopt1) process ;;
(-longopt2) process ;;
esac; done

É claro que, tal como está, isso não funciona se as opções longas tiverem argumentos. Isso pode ser feito, mas, como aprendi trabalhando nisso. Embora inicialmente o tenha incluído aqui, percebi que para opções longas ele não tem muita utilidade. Nesse caso, estava apenas encurtando meus case (match)campos em um caractere único e previsível. Agora, o que eu sei é que é excelente para opções curtas - é mais útil quando está fazendo um loop em uma string de comprimento desconhecido e selecionando bytes únicos de acordo com sua string de opções. Mas quando a opçãoéo argumento, não há muito que você esteja fazendo com uma for var do case $var incombinação que ela possa fazer. É melhor, penso eu, manter as coisas simples.

Suspeito que o mesmo seja verdade, getoptmas não sei o suficiente para dizer com certeza. Dada a matriz de argumentos a seguir, demonstrarei meu próprio pequeno analisador de argumentos - que depende principalmente do relacionamento de avaliação/atribuição que passei a apreciar para aliase $((shell=math)).

set -- this is ignored by default --lopt1 -s 'some '\'' 
args' here --ignored   and these are ignored \
--alsoignored andthis --lopt2 'and 

some "`more' --lopt1 and just a few more

Essa é a string arg com a qual trabalharei. Agora:

aopts() { env - sh -s -- "$@"
} <<OPTCASE 3<<\OPTSCRIPT
acase() case "\$a" in $(fmt='
        (%s) f=%s; aset "?$(($f)):";;\n'
        for a do case "$a" in (--) break;;
        (--*[!_[:alnum:]]*) continue;;
        (--*) printf "$fmt" "$a" "${a#--}";;
        esac;done;printf "$fmt" '--*' ignored)
        (*) aset "" "\$a";;esac
shift "$((SHIFT$$))"; f=ignored; exec <&3 
OPTCASE
aset()  {  alias "$f=$(($f${1:-=$(($f))+}1))"
        [ -n "${2+?}" ] && alias "${f}_$(($f))=$2"; }
for a do acase; done; alias
#END
OPTSCRIPT

Isso processa o array arg de uma de duas maneiras diferentes, dependendo se você entrega a ele um ou dois conjuntos de argumentos separados pelo --delimitador. Em ambos os casos, aplica-se a sequências de processamento para o array arg.

Se você chamar assim:

: $((SHIFT$$=3)); aopts --lopt1 --lopt2 -- "$@"

Sua primeira tarefa será escrever sua acase()função para se parecer com:

acase() case "$a" in 
    (--lopt1) f=lopt1; aset "?$(($f)):";;
    (--lopt2) f=lopt2; aset "?$(($f)):";;
    (--*) f=ignored; aset "?$(($f)):";;
    (*) aset "" "$a";;esac

E ao lado shift 3. A substituição de comando na acase()definição da função é avaliada quando o shell de chamada constrói os documentos de entrada da função aqui, mas acase()nunca é chamado ou definido no shell de chamada. Ele é chamado no subshell, é claro, e dessa forma você pode especificar dinamicamente as opções de interesse na linha de comando.

Se você entregar um array não delimitado, ele simplesmente preencherá acase()com correspondências para todos os argumentos que começam com string --.

A função faz praticamente todo o seu processamento no subshell - salvando iterativamente cada um dos valores do argumento em aliases atribuídos com nomes associativos. Quando termina, ele imprime todos os valores salvos alias- que são especificados pelo POSIX para imprimir todos os valores salvos citados de forma que seus valores possam ser reinseridos no shell. Então quando eu faço...

aopts --lopt1 --lopt2 -- "$@"

Sua saída é semelhante a esta:

...ignored...
lopt1='8'
lopt1_1='-s'
lopt1_2='some '\'' args'
lopt1_3='here'
lopt1_4='and'
lopt1_5='just'
lopt1_6='a'
lopt1_7='few'
lopt1_8='more'
lopt2='1'
lopt2_1='and

some "`more'

À medida que percorre a lista de argumentos, ele verifica se há uma correspondência no bloco case. Se encontrar uma correspondência, ele lança uma bandeira - f=optname. Até encontrar novamente uma opção válida, ele adicionará cada argumento subsequente a um array que constrói com base no sinalizador atual. Se a mesma opção for especificada várias vezes, os resultados serão compostos e não serão substituídos. Qualquer coisa que não esteja no caso - ou quaisquer argumentos após opções ignoradas - são atribuídos a umignoradovariedade.

A saída é protegida pelo shell para entrada do shell automaticamente pelo shell, e assim:

eval "$(: $((SHIFT$$=3));aopts --lopt1 --lopt2 -- "$@")"

...deve ser perfeitamente seguro. Se por algum motivonão éseguro, então você provavelmente deve enviar um relatório de bug ao mantenedor do shell.

Ele atribui dois tipos de valores de alias para cada correspondência. Primeiro, ele define um sinalizador - isso ocorre independentemente de uma opção preceder ou não argumentos não correspondentes. Portanto, qualquer ocorrência --flagna lista de argumentos será acionada flag=1. Isso não é composto - --flag --flag --flagapenas é obtido flag=1. Este valorfazincremento embora - para quaisquer argumentos que possam segui-lo. Pode ser usado como uma chave de índice. Depois de fazer o evalacima, posso fazer:

printf %s\\n "$lopt1" "$lopt2"

...obter...

8
1

E assim:

for o in lopt1 lopt2
do list= i=0; echo "$o = $(($o))"
        while [ "$((i=$i+1))" -le "$(($o))" ]
        do list="$list $o $i \"\${${o}_$i}\" "
done; eval "printf '%s[%02d] = %s\n' $list";  done

SAÍDA

lopt1 = 8
lopt1[01] = -s
lopt1[02] = some ' args
lopt1[03] = here
lopt1[04] = and
lopt1[05] = just
lopt1[06] = a
lopt1[07] = few
lopt1[08] = more
lopt2 = 1
lopt2[01] = and

some "`more

E para argumentos que não correspondessem eu substituiriaignoradono campo acima for ... inpara obter:

ignored = 10
ignored[01] = this
ignored[02] = is
ignored[03] = ignored
ignored[04] = by
ignored[05] = default
ignored[06] = and
ignored[07] = these
ignored[08] = are
ignored[09] = ignored
ignored[10] = andthis

informação relacionada