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

13

Há 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 preciso de opções longas (ou seja, opções curtas não existem), no entanto, o comando getopt -l long-key -- "$@"não analisar a --long-keyopção corretamente. Então, como posso usar o getoptcomando apenas com opções longas? Ou é impossível ou é apenas um bug do getoptcomando?

Vencedor
fonte
Você marca o interno getopts, mas está usando o /usr/bin/getoptcomando
Anthon
@ Anthon Desculpe, eu usei a tag errada, mas não tenho reputação suficiente para adicionar outra tag, que precisa de 300 reputações. No entanto, excluí a tag errada agora.
213 Victor Victor

Respostas:

15

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

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

É isso que está acontecendo no seu teste: getopt -l long-key -- --long-key footrata --long-keycomo a lista de opções -egklnoye foocomo o ú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'
Gilles 'SO- parar de ser mau'
fonte
O OP misturou getoptse getoptinfectou sua resposta? Você começa comentando e getoptsdepois apenas menciona getopt.
Anthon
@Anthon Toda a minha resposta é sobre o getoptprograma GNU coreutils, que é o que a questão se refere. Corrigi o texto que dizia getopts. Obrigado. getoptsnem faz opções longas, então nada disso se aplicaria a getopts.
Gilles 'SO- stop be evil'
O OP originalmente tinha a getoptsetiqueta. Eu não quero mudar sua resposta, porque você normalmente sabe muito melhor do que o que você está escrevendo sobre :-)
Anthon
Perdi quase uma hora tentando descobrir isso. Você acabou de me salvar alguns goles de café vãos. Obrigado ... vamos usar melhor este café agora. ☕️
dmmd
1

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

Obviamente, como está, isso não funciona se as opções longas tiverem argumentos. Isso pode ser feito, porém, mas, como aprendi a trabalhar nisso. Enquanto o incluí inicialmente aqui, percebi que, para opções longas, ele não tem muita utilidade. Nesse caso, estava apenas diminuindo meus case (match)campos com um único caractere previsível. Agora, o que eu sei é que é excelente para opções curtas - é mais útil quando ele faz um loop sobre uma sequência de tamanho desconhecido e seleciona bytes únicos de acordo com a sequência de opções. Mas quando a opção é o argumento, não há muito o que você está fazendo com uma for var do case $var incombinação que ele possa fazer. Acho que é melhor manter as coisas simples.

Suspeito que o mesmo seja verdade, getoptmas não sei o suficiente para dizer com certeza. Dada a seguinte matriz de argumentos, demonstrarei meu próprio analisador de argumentos - que depende principalmente da relação de avaliação / atribuição pela qual apreciei 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 a matriz arg de uma de duas maneiras diferentes, dependendo se você entrega um ou dois conjuntos de argumentos separados pelo --delimitador. Nos dois casos, aplica-se a sequências de processamento na matriz arg.

Se você chamar assim:

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

Sua primeira ordem de negócios 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 de shift 3. A substituição de comando na acase()definição da função é avaliada quando o shell de chamada constrói aqui os documentos de entrada da função, mas acase()nunca é chamado ou definido no shell de chamada. É chamado no subshell, é claro, e dessa forma você pode especificar dinamicamente as opções de interesse na linha de comando.

Se você entregar uma matriz não delimitada, ela simplesmente será preenchida acase()com correspondências para todos os argumentos que começam com a sequência --.

A função realiza praticamente todo o seu processamento no subshell - salvando iterativamente cada um dos valores do arg em aliases atribuídos com nomes associativos. Quando aliastermina , imprime todos os valores salvos com - especificados no POSIX para imprimir todos os valores salvos entre aspas, de forma que seus valores possam ser reinicializados no shell. Então, quando eu faço ...

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

Sua saída é assim:

...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'

Enquanto percorre a lista arg, verifica se há uma correspondência no bloco de casos. Se encontrar uma partida, lança uma bandeira - f=optname. Até encontrar novamente uma opção válida, ele adicionará cada argumento subsequente a uma matriz criada com base no sinalizador atual. Se a mesma opção for especificada várias vezes, os resultados serão compostos e não substituirão. Qualquer coisa que não esteja no caso - ou qualquer argumento após as opções ignoradas - é atribuída a uma matriz ignorada .

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 motivo, não for seguro, provavelmente você deve registrar um relatório de erro com o mantenedor do shell.

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

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

...para obter...

8
1

E entao:

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

RESULTADO

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 args que não correspondiam eu substituiria ignorado no for ... incampo acima para 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
mikeserv
fonte