getopt, getopts ou análise manual - o que usar quando quero suportar opções curtas e longas?

28

Atualmente estou escrevendo um script Bash que possui os seguintes requisitos:

  • deve ser executado em uma ampla variedade de plataformas Unix / Linux
  • deve suportar opções curtas e (GNU) longas

Eu sei que getopts seria o caminho preferido em termos de portabilidade, mas a AFAIK não suporta opções longas.

getopt suporta opções longas, mas o BashGuide recomenda veementemente:

Never use getopt(1). getopt cannot handle empty arguments strings, or arguments with embedded whitespace. Please forget that it ever existed.

Então, ainda há a opção de análise manual. Isso é propenso a erros, produz bastante código clichê, e eu preciso lidar com erros por mim mesmo (eu acho que getopt(s) faz o tratamento de erros por conta própria).

Então, qual seria a escolha preferida neste caso?

    
por helpermethod 29.01.2013 / 12:37

5 respostas

10

Se for portável para uma gama de Unices, você terá que se ater ao POSIX sh. E AFAIU lá você simplesmente não tem escolha a não ser manipular argumento manualmente.

    
por 29.01.2013 / 14:19
23

getopt vs getopts parece ser uma questão religiosa. Quanto aos argumentos contra getopt na FAQ da Bash :

  • " getopt não suporta strings de argumentos vazios" parece referir-se a um problema conhecido com argumentos optional , que parece que getopts não suporta de todo (pelo menos da leitura help getopts para o Bash 4.2.24). De man getopt :

    getopt(3) can parse long options with optional arguments that are given an empty optional argument (but can not do this for short options). This getopt(1) treats optional arguments that are empty as if they were not present.

Eu não sei onde o " getopt não pode lidar [...] com argumentos com espaço em branco embutido" vem, mas vamos testá-lo:

  • test.sh:

    #!/usr/bin/env bash
    set -o errexit -o noclobber -o nounset -o pipefail
    params="$(getopt -o ab:c -l alpha,bravo:,charlie --name "$0" -- "$@")"
    eval set -- "$params"
    
    while true
    do
        case "$1" in
            -a|--alpha)
                echo alpha
                shift
                ;;
            -b|--bravo)
                echo "bravo=$2"
                shift 2
                ;;
            -c|--charlie)
                echo charlie
                shift
                ;;
            --)
                shift
                break
                ;;
            *)
                echo "Not implemented: $1" >&2
                exit 1
                ;;
        esac
    done
    
  • executar:

    $ ./test.sh -
    $ ./test.sh -acb '   whitespace   FTW   '
    alpha
    charlie
    bravo=   whitespace   FTW   
    $ ./test.sh -ab '' -c
    alpha
    bravo=
    charlie
    $ ./test.sh --alpha --bravo '   whitespace   FTW   ' --charlie
    alpha
    bravo=   whitespace   FTW   
    charlie
    

Parece checar e acasalar comigo, mas tenho certeza que alguém vai mostrar como eu entendi completamente a frase. Claro que a questão da portabilidade ainda permanece; você terá que decidir quanto tempo vale a pena investir em plataformas com um Bash antigo ou não disponível. Minha dica é usar o YAGNI e KISS - Desenvolva-se apenas para as plataformas específicas que você sabe que serão usadas. A portabilidade do código shell geralmente vai para 100%, conforme o tempo de desenvolvimento vai para o infinito.

    
por 29.01.2013 / 13:25
9

Há este getopts_long escrito como uma função de shell POSIX que você pode inserir dentro do seu script.

Observe que o Linux getopt (de util-linux ) funciona corretamente quando não está no modo tradicional e oferece suporte a opções longas, mas provavelmente não é uma opção para você se precisar ser portátil para outros Unices.

As versões recentes de ksh93 ( getopts ) e zsh ( zparseopts ) têm suporte interno para analisar opções longas que podem ser uma opção para você, pois estão disponíveis para a maioria dos Unices (embora não sejam instaladas por padrão) .

Outra opção seria usar perl e seu módulo Getopt::Long , que devem estar disponíveis na maioria dos Unices hoje, seja escrevendo o script inteiro em perl ou apenas chamar perl apenas para analisar a opção e o feed a informação extraída para o shell. Algo como:

parsed_ops=$(
  perl -MGetopt::Long -le '

    @options = (
      "foo=s", "bar", "neg!"
    );

    Getopt::Long::Configure "bundling";
    $q="'\''";
    GetOptions(@options) or exit 1;
    for (map /(\w+)/, @options) {
      eval "\$o=\$opt_$_";
      $o =~ s/$q/$q\$q$q/g;
      print "opt_$_=$q$o$q"
    }' -- "$@"
) || exit
eval "$parsed_ops"
# and then use $opt_foo, $opt_bar...

Veja perldoc Getopt::Long para o que ele pode fazer e como ele difere de outros analisadores de opções.

    
por 29.01.2013 / 13:24
2

Todas as discussões sobre esse assunto destacam a opção de escrever o código de análise manualmente - só assim você pode ter certeza sobre a funcionalidade e a portabilidade. Eu aconselho você a não escrever código que possa ser gerado e re-gerado por geradores de código de código aberto fáceis de usar. Use o Argbash , que foi criado para fornecer a resposta definitiva para o seu problema. É um gerador de códigos bem documentado disponível como aplicativo de linha de comando , on-line ou como um Imagem do compartimento .

Eu aconselho contra bibliotecas bash, algumas delas usam getopt (o que torna bastante não-portável) e é difícil empacotar um gigantesco e ilegível blob shell com seu script.

    
por 16.11.2017 / 00:48
0

Você pode usar getopt em sistemas que o suportam e usar um fallback para sistemas que não o fazem.

Por exemplo pure-getopt é implementado no Bash puro para substituir o GNU getopt .

    
por 22.07.2017 / 14:53