getopt, getopts ou análise manual - o que usar quando desejar oferecer suporte a opções curtas e longas?

32

Atualmente, estou escrevendo um script Bash com os seguintes requisitos:

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

Eu sei que getoptsseria a maneira preferida em termos de portabilidade, mas o AFAIK não suporta opções longas.

getoptsuporta opções longas, mas o BashGuide recomenda fortemente:

Nunca use getopt (1). O getopt não pode manipular cadeias de argumentos vazias ou argumentos com espaço em branco incorporado. Por favor, esqueça que ele já existiu.

Portanto, ainda há a opção de análise manual. Isso é propenso a erros, produz bastante código padrão, e eu preciso lidar com os erros sozinho (acho getopt(s)que o tratamento de erros sozinho).

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

helpermethod
fonte
relacionado: stackoverflow.com/questions/192249/…
Ciro Santilli escreveu:

Respostas:

9

Se precisar ser portátil para uma variedade de Unices, você precisará seguir o POSIX sh. E no AFAIU, você simplesmente não tem escolha, a não ser manipular argumentos manualmente.

vonbrand
fonte
25

getoptvs getoptsparece ser uma questão religiosa. Quanto aos argumentos contra getoptno Bash FAQ :

  • " getoptnão pode lidar com cadeias de argumentos vazias" parece se referir a um problema conhecido com argumentos opcionais , que parece getoptsnão ter suporte (pelo menos na leitura help getoptsdo Bash 4.2.24). De man getopt:

    O getopt (3) pode analisar opções longas com argumentos opcionais que recebem um argumento opcional vazio (mas não podem fazer isso para opções curtas). Este getopt (1) trata argumentos opcionais que estão vazios como se não estivessem presentes.

Não sei de onde getoptvem o " não pode lidar com [...] argumentos com espaço em branco incorporado", 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
  • corre:

    $ ./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 verificar 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 mais antigo ou sem disponibilidade. Minha dica é usar as diretrizes YAGNI e KISS - Desenvolva apenas para plataformas específicas que você sabe que serão usadas. A portabilidade do código do shell geralmente chega a 100% conforme o tempo de desenvolvimento chega ao infinito.

l0b0
fonte
11
O OP mencionou a necessidade de ser portátil para muitas plataformas Unix, enquanto o que getoptvocê está citando aqui é específico do Linux. Note que isso getoptnão faz parte bash, nem é um utilitário GNU e no Linux é fornecido com o pacote util-linux.
Stéphane Chazelas
2
Na maioria das plataformas getopt, apenas o Linux AFAIK vem com um que suporta opções longas ou espaços em branco nos argumentos. Os outros suportariam apenas a sintaxe do System V.
Stéphane Chazelas
7
getopté um comando tradicional que vem do System V muito antes do Linux ser lançado. getoptnunca foi padronizado. Nenhum POSIX, Unix ou Linux (LSB) padronizou o getoptcomando. getoptsé especificado nos três, mas sem suporte para opções longas.
Stéphane Chazelas
1
Apenas para adicionar a esta discussão: este não é o tradicional getopt. É o sabor linux-utils, conforme indicado por @ StéphaneChazelas. Possui opções herdadas que desabilitam a sintaxe descrita acima, em particular a página de manual declara "GETOPT_COMPATIBLE força o getopt a usar o primeiro formato de chamada, conforme especificado na SINOPSE". No entanto, se você pode esperar sistemas de destino para ter este pacote instalado isso é totalmente o caminho a percorrer, como o getopt original está horrível e getopt do Bash é muito limitado
n.caillou
1
O link do OP que afirma "As versões tradicionais do getopt não podem manipular cadeias de argumentos vazias ou argumentos com espaço em branco incorporado". e que a versão util-linux do getopt não deve ser usada não possui evidências e não é mais precisa. Uma pesquisa rápida (mais de 5 anos depois) mostra que a versão padrão do getopt disponível no ArchLinux, SUSE, Ubuntu, RedHat, Fedora e CentOS (assim como na maioria das variantes derivativas) suporta argumentos e argumentos opcionais com espaço em branco.
precisa saber é o seguinte
10

Há esse getopts_long escrito como uma função de shell POSIX que você pode incorporar ao seu script.

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

As versões recentes do 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 muitas vezes não sejam instaladas por padrão).

Outra opção seria usar perle seu Getopt::Longmódulo, que deve estar disponível na maioria dos Unices atualmente, escrevendo o script inteiro perlou apenas chamando perl apenas para analisar a opção e alimentar as informações extraídas 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 o perldoc Getopt::Longque ele pode fazer e como ele difere de outros analisadores de opções.

Stéphane Chazelas
fonte
2

Toda discussão sobre esse assunto destaca a opção de escrever o código de análise manualmente - somente então você pode ter certeza sobre a funcionalidade e a portabilidade. Eu aconselho você a não escrever código que possa ter gerado e recriado por geradores de código-fonte aberto fáceis de usar. Use Argbash , que foi projetado para fornecer a resposta definitiva para o seu problema. É um gerador de código bem documentado disponível como um aplicativo de linha de comando , online ou como uma imagem do Docker .

Eu desaconselho as bibliotecas bash, algumas delas usam getopt(o que torna bastante portável) e é doloroso agrupar um blob gigantesco de shell ilegível com seu script.

bubla
fonte
0

Você pode usar getoptem sistemas que o suportam e usar um fallback para sistemas que não o suportam.

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

Potherca
fonte