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

10

Existe um comando getopt na linha de comando do bash. getopt pode 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 apenas de somente opções longas (ou seja opções curtas não existem, no entanto, o comando getopt -l long-key -- "$@" não analisa --long-key corretamente. Então, como posso usar o comando getopt com as opções longas somente ? Ou é impossível ou é apenas um erro do comando getopt ?

    
por Victor 17.10.2014 / 02:22

2 respostas

13

getopt é perfeitamente aceitável por não ter opções curtas. Mas você precisa dizer que não tem opções curtas. É um capricho na sintaxe - do manual:

If no -o or --options option is found in the first part, the first parameter of the second part is used as the short options string.

Isso é o que está acontecendo no seu teste: getopt -l long-key -- --long-key foo trata --long-key como a lista de opções -egklnoy e foo como o único argumento. Use

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'
    
por 18.10.2014 / 02:03
1

Não sei sobre getopt , mas o getopts builtin 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, como é, isso não funciona se as opções longas tiverem argumentos. Isso pode ser feito, porém, como aprendi trabalhando nisso. Embora eu inicialmente tenha incluído aqui, percebi que, para opções longas, não tem muita utilidade. Nesse caso, estava apenas encurtando meus campos case (match) por um único caractere previsível. Agora, o que eu sei, é que é excelente para opções curtas - é mais útil quando está em loop em uma cadeia de comprimento desconhecido e selecionando bytes simples de acordo com sua string de opção. Mas quando a opção é o arg, não há muita coisa que você esteja fazendo com uma combinação for var do case $var in que poderia fazer. É melhor, penso eu, manter isso simples.

Eu suspeito que o mesmo acontece com getopt , mas eu não sei o suficiente para dizer com certeza. Dada a seguinte matriz arg, demonstrarei meu próprio analisador de argos - que depende principalmente da relação de avaliação / atribuição que passei a apreciar para alias e $((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 que eu vou trabalhar. 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 duas maneiras diferentes, dependendo se você a entrega a um ou dois conjuntos de argumentos separados pelo delimitador -- . Em ambos os casos, aplica-se a seqüências de processamento para o array arg.

Se você ligar assim:

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

Sua primeira ordem de negócio será escrever sua função acase() 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 definição da função acase() é avaliada quando o shell de chamada constrói a entrada da função here-documents, mas acase() nunca é chamado ou definido no shell de chamada. É chamado no subshell, é claro, e assim você pode especificar dinamicamente as opções de interesse na linha de comando.

Se você entregar uma matriz não delimitada, ele simplesmente preencherá acase() com correspondências para todos os argumentos iniciados com a string -- .

A função faz praticamente todo o seu processamento no subshell - salvando iterativamente cada um dos valores do arg para aliases designados com nomes associativos. Quando é através dele imprime todos os valores salvos com alias - que é POSIX-especificado para imprimir todos os valores salvos citados de tal forma que seus valores podem ser reinputados para o 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'

Conforme percorre a lista de argumentos, verifica o bloco de casos para uma correspondência. Se encontrar uma correspondência lá, ele lança um sinalizador - f=optname . Até encontrar novamente uma opção válida, ele adicionará cada argumento subseqüente a uma matriz que ele cria 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 qualquer argumento após as opções ignoradas - é atribuído a uma matriz ignorada .

A saída é protegida por shell para entrada de shell automaticamente pelo shell e, portanto:

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

... deve ser perfeitamente seguro. Se por algum motivo não for seguro, então você provavelmente deve enviar um relatório de bug ao seu 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 correspondentes. Portanto, qualquer ocorrência de --flag na lista de argumentos acionará flag=1 . Isso não é composto - --flag --flag --flag apenas obtém flag=1 . Este valor faz incrementar - para quaisquer argumentos que possam segui-lo. Pode ser usado como uma chave de índice. Depois de fazer o eval acima, posso fazer:

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

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

OUTPUT

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 campo for ... in 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
    
por 17.10.2014 / 05:19

Tags