getopt, getopts oder manuelles Parsen - was soll ich verwenden, wenn ich sowohl kurze als auch lange Optionen unterstützen möchte?

32

Derzeit schreibe ich ein Bash-Skript, das die folgenden Anforderungen erfüllt:

  • Es sollte auf einer Vielzahl von Unix / Linux-Plattformen laufen
  • Es sollte sowohl kurze als auch (GNU) lange Optionen unterstützen

Ich weiß, dass getoptsdies in Bezug auf die Portabilität der bevorzugte Weg wäre, aber AFAIK unterstützt keine langen Optionen.

getoptunterstützt lange Optionen, aber der BashGuide rät dringend davon ab:

Verwenden Sie niemals getopt (1). getopt kann keine leeren Argumente, Strings oder Argumente mit eingebettetem Whitespace verarbeiten. Bitte vergessen Sie, dass es jemals existiert hat.

Es besteht also weiterhin die Möglichkeit des manuellen Parsens. Dies ist fehleranfällig, führt zu ziemlich viel Code auf der Kesselplatte und ich muss Fehler selbst behandeln (ich denke getopt(s), Fehler werden von selbst behoben).

Was wäre in diesem Fall die bevorzugte Wahl?

Hilfemethode
quelle

Antworten:

9

Wenn es auf eine Reihe von Unices portierbar sein soll, müssen Sie sich an POSIX sh halten. Und AFAIU dort haben Sie einfach keine andere Wahl, als das rollierende Argumentieren von Hand.

vonbrand
quelle
25

getoptvs getoptsscheint eine religiöse Angelegenheit zu sein. Zu den Argumenten dagegen getoptin den Bash-FAQ :

  • " getoptKann keine leeren Argumente verarbeiten" bezieht sich anscheinend auf ein bekanntes Problem mit optionalen Argumenten, das anscheinend getoptsüberhaupt nicht unterstützt wird (zumindest beim Lesen help getoptsfür Bash 4.2.24). Von man getopt:

    getopt (3) kann lange Optionen mit optionalen Argumenten analysieren, die ein leeres optionales Argument erhalten (dies ist jedoch für kurze Optionen nicht möglich). Dieses getopt (1) behandelt optionale Argumente, die leer sind, als ob sie nicht vorhanden wären.

Ich weiß nicht, woher die getopt[...] Argumente mit eingebettetem Whitespace kommen, aber testen wir sie:

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

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

Sieht für mich nach Scheck und Kumpel aus, aber ich bin sicher, jemand wird zeigen, wie ich den Satz völlig missverstanden habe. Natürlich bleibt das Problem der Portabilität bestehen. Sie müssen entscheiden, wie viel Zeit es wert ist, in Plattformen mit einem älteren oder keinem verfügbaren Bash zu investieren. Mein eigener Tipp ist, die YAGNI- und KISS- Richtlinien zu verwenden - Entwickeln Sie nur für die spezifischen Plattformen, von denen Sie wissen, dass sie verwendet werden. Die Portabilität von Shell-Code beträgt im Allgemeinen 100%, da die Entwicklungszeit unendlich ist.

l0b0
quelle
11
Das OP erwähnte die Notwendigkeit, auf viele Unix-Plattformen portierbar zu sein, während das, was getoptSie hier zitieren, Linux-spezifisch ist. Beachten Sie, dass dies getoptnicht Teil von ist bash, nicht einmal ein GNU-Dienstprogramm und unter Linux mit dem Paket util-linux ausgeliefert wird.
Stéphane Chazelas
2
Die meisten Plattformen haben getopt, nur Linux AFAIK kommt mit einer, die lange Optionen oder Leerzeichen in Argumenten unterstützt. Die anderen unterstützen nur die System V-Syntax.
Stéphane Chazelas
7
getoptist ein traditioneller Befehl, der lange vor der Veröffentlichung von Linux von System V stammt. getoptwurde nie standardisiert. Keiner von POSIX, Unix oder Linux (LSB) hat den getoptBefehl jemals standardisiert . getoptswird in allen drei angegeben, jedoch ohne Unterstützung für lange Optionen.
Stéphane Chazelas
1
Nur um diese Diskussion zu ergänzen: Dies ist nicht die traditionelle getopt. Es ist das Linux-Utensilien-Aroma, wie es von @ StéphaneChazelas angezeigt wird. Es verfügt über Legacy-Optionen, die die oben beschriebene Syntax deaktivieren. Insbesondere die Manpage "GETOPT_COMPATIBLE" erzwingt, dass getopt das erste in SYNOPSIS angegebene Aufrufformat verwendet. Wenn Sie jedoch erwarten können, dass auf den Zielsystemen dieses Paket installiert ist, ist dies der richtige Weg, da das ursprüngliche getopt schrecklich und das getopt von Bash sehr begrenzt ist
n.caillou
1
OPs Link mit der Behauptung "Traditionelle Versionen von getopt können keine leeren Argumente oder Argumente mit eingebettetem Leerzeichen verarbeiten." und dass die util-linux-version von getopt nicht verwendet werden sollte, hat keine beweise und ist nicht mehr korrekt. Eine kurze Umfrage (mehr als 5 Jahre später) zeigt, dass die Standardversion von getopt, die unter ArchLinux, SUSE, Ubuntu, RedHat, Fedora und CentOS erhältlich ist (sowie die meisten abgeleiteten Varianten), optionale Argumente und Argumente mit Leerzeichen unterstützen.
Mtalexan
10

Es gibt diese getopts_long als POSIX-Shell-Funktion, die Sie in Ihr Skript einbetten können.

Beachten Sie, dass Linux getopt(von util-linux) korrekt funktioniert, wenn es sich nicht im herkömmlichen Modus befindet und lange Optionen unterstützt. Es ist jedoch wahrscheinlich keine Option für Sie, wenn Sie auf andere Unices portierbar sein müssen.

Neuere Versionen von ksh93 ( getopts) und zsh ( zparseopts) bieten eine integrierte Unterstützung für das Parsen langer Optionen, die für Sie möglicherweise eine Option sind, da sie für die meisten Unices verfügbar sind (obwohl sie häufig nicht standardmäßig installiert sind).

Eine andere Option wäre die Verwendung perlund Getopt::Longdas zugehörige Modul, die heutzutage auf den meisten Unices verfügbar sein sollten, entweder durch Schreiben des gesamten Skripts perloder einfach durch Aufrufen von Perl, um die Option zu analysieren und die extrahierten Informationen an die Shell weiterzuleiten . So etwas wie:

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

Sehen Sie, perldoc Getopt::Longwas es kann und wie es sich von anderen Optionsparsern unterscheidet.

Stéphane Chazelas
quelle
2

In jeder Diskussion zu diesem Thema wird die Option hervorgehoben, den Parsing-Code manuell zu schreiben - nur dann können Sie sich über die Funktionalität und Portabilität sicher sein. Ich rate Ihnen, keinen Code zu schreiben, den Sie mit benutzerfreundlichen Open-Source-Codegeneratoren generieren und erneut generieren können. Verwenden Sie Argbash , um die endgültige Antwort auf Ihr Problem zu finden. Es ist ein gut dokumentierter Codegenerator, der als Befehlszeilenanwendung , online oder als Docker-Image verfügbar ist .

Ich rate von Bash-Bibliotheken ab, von denen einige verwenden getopt(was ziemlich unportabel ist), und es ist mühsam, einen gigantischen unlesbaren Shell-Blob mit Ihrem Skript zu bündeln.

bubla
quelle
0

Sie können es getoptauf Systemen verwenden, die es unterstützen, und ein Fallback für Systeme verwenden, die dies nicht tun.

Zum Beispiel pure-getoptwird in pure Bash implementiert, um ein Ersatz für GNU zu sein getopt.

Potherca
quelle