getopt, getopts o analisi manuale: cosa usare quando voglio supportare le opzioni short e long?


32

Attualmente sto scrivendo uno script Bash che ha i seguenti requisiti:

  • dovrebbe funzionare su un'ampia varietà di piattaforme Unix / Linux
  • dovrebbe supportare entrambe le opzioni short e (GNU) long

So che getoptssarebbe il modo preferito in termini di portabilità ma AFAIK non supporta opzioni lunghe.

getoptsupporta opzioni lunghe ma BashGuide sconsiglia vivamente:

Non usare mai getopt (1). getopt non può gestire stringhe di argomenti vuote o argomenti con spazi bianchi incorporati. Per favore, dimentica che è mai esistito.

Quindi, c'è ancora l'opzione di analisi manuale. Questo è soggetto a errori, produce un po 'di codice boilerplate e ho bisogno di gestire gli errori da solo (immagino getopt(s)che gestiscano gli errori da soli).

Quindi, quale sarebbe la scelta preferita in questo caso?


Risposte:


9

Se deve essere portatile su una gamma di Unices, dovresti attenersi a POSIX sh. E AFAIU lì non hai altra scelta che rotolare la gestione degli argomenti a mano.


25

getoptvs getoptssembra essere un problema religioso. Per quanto riguarda gli argomenti contrari getoptnelle FAQ di Bash :

  • " getoptimpossibile gestire stringhe di argomenti vuote" sembra riferirsi a un problema noto con argomenti opzionali , che a quanto pare getoptsnon supporta affatto (almeno dalla lettura help getoptsdi Bash 4.2.24). Da man getopt:

    getopt (3) può analizzare le opzioni lunghe con argomenti opzionali a cui viene fornito un argomento opzionale vuoto (ma non può farlo per le opzioni brevi). Questo getopt (1) tratta argomenti opzionali che sono vuoti come se non fossero presenti.

Non so da dove getoptprovenga l '" impossibile gestire [...] argomenti con spazi bianchi incorporati", ma proviamo:

  • 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
    
  • correre:

    $ ./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
    

Mi sembra di controllare e accoppiarmi, ma sono sicuro che qualcuno mostrerà come ho frainteso completamente la frase. Naturalmente il problema della portabilità è ancora valido; dovrai decidere quanto tempo vale la pena investire in piattaforme con un Bash più vecchio o non disponibile. Il mio consiglio è di usare le linee guida YAGNI e KISS - Sviluppa solo per quelle piattaforme specifiche che sai che verranno utilizzate. La portabilità del codice shell generalmente va al 100% man mano che il tempo di sviluppo va all'infinito.


11
L'OP ha menzionato la necessità di essere portabile su molte piattaforme Unix mentre quello getoptche stai citando qui è specifico di Linux. Nota che getoptnon fa parte di bash, non è nemmeno un'utilità GNU e su Linux viene fornito con il pacchetto util-linux.
Stéphane Chazelas,

2
La maggior parte delle piattaforme ha getopt, solo Linux AFAIK ne ha uno che supporta lunghe opzioni o spazi vuoti negli argomenti. Gli altri supporterebbero solo la sintassi di System V.
Stéphane Chazelas,

7
getoptè un comando tradizionale che proviene da System V molto prima che Linux fosse mai rilasciato. getoptnon è mai stato standardizzato. Nessuno di POSIX, Unix o Linux (LSB) ha mai standardizzato il getoptcomando. getoptsè specificato in tutti e tre ma senza supporto per le opzioni lunghe.
Stéphane Chazelas,

1
Solo per aggiungere a questa discussione: questo non è il tradizionale getopt. È il sapore di Linux-utils come indicato da @ StéphaneChazelas. Ha opzioni legacy che disabiliteranno la sintassi sopra descritta, in particolare gli stati manpage "GETOPT_COMPATIBLE impongono a getopt di utilizzare il primo formato di chiamata come specificato nella SINOSSI". Tuttavia, se puoi aspettarti che i sistemi target abbiano questo pacchetto installato, questa è la strada da percorrere, dato che la getopt originale è terribile e la getopt di Bash è molto limitata
n.caillou,

1
Link di OP che afferma "Le versioni tradizionali di getopt non sono in grado di gestire stringhe di argomenti vuote o argomenti con spazi bianchi incorporati". e che la versione util-linux di getopt non dovrebbe essere usata non ha prove e non è più accurata. Un rapido sondaggio (5+ anni dopo) mostra che la versione predefinita di getopt disponibile da ArchLinux, SUSE, Ubuntu, RedHat, Fedora e CentOS (così come la maggior parte delle varianti derivate) supportano tutti argomenti e argomenti opzionali con spazi bianchi.
mtalexan,

10

C'è questo getopts_long scritto come una funzione shell POSIX che puoi incorporare nel tuo script.

Nota che Linux getopt(da util-linux) funziona correttamente quando non è in modalità tradizionale e supporta opzioni lunghe, ma probabilmente non è un'opzione per te se devi essere portabile su altri Unices.

Le versioni recenti di ksh93 ( getopts) e zsh ( zparseopts) hanno il supporto integrato per l'analisi di opzioni lunghe che potrebbero essere un'opzione per te poiché sono disponibili per la maggior parte degli Unices (anche se spesso non sono installati di default).

Un'altra opzione sarebbe quella di utilizzare perle il suo Getopt::Longmodulo, entrambi i quali dovrebbero essere disponibili sulla maggior parte dei Unices al giorno d'oggi, o scrivendo l'intero script perlo semplicemente chiamando perl solo per analizzare l'opzione e alimentare le informazioni estratte nella shell. Qualcosa di simile a:

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

Scopri perldoc Getopt::Longcosa può fare e come differisce dagli altri parser di opzioni.


2

Ogni discussione su questo argomento evidenzia la possibilità di scrivere manualmente il codice di analisi - solo allora puoi essere sicuro della funzionalità e della portabilità. Ti consiglio di non scrivere il codice che puoi aver generato e rigenerato da generatori di codice open source di facile utilizzo. Usa Argbash , che è stato progettato per fornire la risposta definitiva al tuo problema. È un generatore di codice ben documentato disponibile come applicazione da riga di comando , online o come immagine Docker .

Sconsiglio alle librerie di bash, alcune delle quali usano getopt(il che rende abbastanza inesplicabili) ed è una seccatura raggruppare un gigantesco blob di shell illeggibile con il tuo script.


0

È possibile utilizzare getoptsu sistemi che lo supportano e utilizzare un fallback per i sistemi che non lo supportano.

Ad esempio, pure-getoptè implementato in puro Bash per essere un sostituto drop-in per GNU getopt.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.