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

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

Comentários

  • Você tag para o getopts interno, mas você está usando o comando /usr/bin/getopt.
  • @Anthon Desculpe, usei a tag errada, mas não ' não tenho reputação suficiente para adicionar outra tag, que precisa de 300 reputações. No entanto, excluí a tag errada agora.

Resposta

getopt está 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 não -o ou --options opção é encontrada na primeira parte, o primeiro parâmetro da segunda parte é usado como a string de opções curta.

É isso que está acontecendo em 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" 

Comentários

  • O OP ' s mistura de getopts e getopt infectar sua resposta? Você começa comentando getopts e depois menciona apenas getopt.
  • @Anthon Todas as minhas respostas são sobre o getopt programa GNU coreutils, que é sobre o que a pergunta se trata. I corrigimos o texto que dizia getopts. Obrigado. getopts não ' nem faz opções longas, então nada disso se aplicaria a getopts .
  • O OP originalmente tinha a tag getopts. Não queria mudar sua resposta, porque você normalmente sabe muito melhor do que eu sobre o que está escrevendo 🙂
  • Perdi quase uma hora tentando descobrir isso. Você acabou de me poupar alguns goles de café em vão. Obrigado … vamos aproveitar melhor este café agora. ☕️

Resposta

Não sei sobre getopt, mas a getopts embutido 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, como está, isso não ” t funciona se as opções longas devem ter argumentos. Pode ser feito, mas, como aprendi trabalhando nisso. Embora eu inicialmente o incluísse aqui, percebi que para opções longas, ele não tem muita utilidade. Nesse caso, ele estava apenas encurtando meu case (match) campos por um único caractere 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ção . Mas quando a opção é o argumento, “não há muito que você” esteja fazendo com uma for var do case $var in combinação que ela possa fazer. É melhor, Eu acho que, para mantê-lo simples.

Suspeito que o mesmo se aplica a getopt, mas não sei o suficiente para dizer com certeza. Dada a seguinte matriz de arg, demonstrarei meu próprio analisador de arg – 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 de argumento com a qual irei 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 o array arg em uma de duas maneiras diferentes, dependendo se você lhe entrega um ou dois conjuntos de argumentos separados pelo delimitador --. Em ambos os casos, ele se aplica a sequências de processamento para a matriz arg.

Se você chamá-lo assim:

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

Sua primeira ordem de o negócio 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 cria os documentos-aqui de entrada da função, 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ê fornecer um array não delimitado, ele simplesmente preenche acase() com correspondências para todos os argumentos que começam com a string --.

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

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

Sua saída é parecida com 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, verifica se há correspondência no bloco de caso. Se encontrar uma correspondência lá, ele lançará uma bandeira – f=optname. Até que encontre novamente uma opção válida, ele adicionará cada arg subsequente a um array que ele 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 seguindo opções ignoradas – são atribuídos a um array ignorado .

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

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

… deve ser perfeitamente seguro. Se por alguma razão 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 aliases para cada combinação. Primeiro, ele define um sinalizador – isso ocorre quer uma opção preceda ou não os argumentos não correspondentes. Portanto, qualquer ocorrência de --flag na lista de argumentos acionará flag=1. Isso não se compõe – --flag --flag --flag apenas obtém flag=1. Este valor não incrementa – para quaisquer argumentos que possam segui-lo. Ele 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 então:

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 correspondam, 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 

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *