Verwenden von getopts zum Verarbeiten langer und kurzer Befehlszeilenoptionen

410

Ich möchte, dass lange und kurze Formen von Befehlszeilenoptionen mit meinem Shell-Skript aufgerufen werden.

Ich weiß, dass das getoptsverwendet werden kann, aber wie in Perl war ich nicht in der Lage, dasselbe mit Shell zu tun.

Irgendwelche Ideen, wie dies gemacht werden kann, damit ich Optionen verwenden kann wie:

./shell.sh --copyfile abc.pl /tmp/
./shell.sh -c abc.pl /tmp/

Oben bedeuten beide Befehle dasselbe für meine Shell, aber mit getoptskonnte ich diese nicht implementieren?

gagneet
quelle
2
IMHO ist die akzeptierte Antwort nicht die beste. Es wird nicht gezeigt, wie getopts verwendet wird, um sowohl "-" als auch "-" Argumente zu verarbeiten. Dies ist möglich, wie @Arvid Requate gezeigt hat. Ich füge eine andere Antwort mit einem ähnlichen Konzept ein, befasse mich aber auch mit dem Benutzerfehler "Vergessen", Werte für Argumente einzufügen, die benötigt werden. Entscheidender Punkt: Getopts können zum Arbeiten gebracht werden. Benutzer sollten stattdessen "getopt" vermeiden, wenn plattformübergreifende Portabilität erforderlich ist. Außerdem ist getopts Teil des POSIX-Standards für Shells, sodass es wahrscheinlich portabel ist.
Pauljohn32

Antworten:

304

Es gibt drei Implementierungen, die in Betracht gezogen werden können:

  • Bash eingebaut getopts. Dies unterstützt keine langen Optionsnamen mit dem Doppelstrichpräfix. Es werden nur Einzelzeichenoptionen unterstützt.

  • BSD UNIX-Implementierung eines eigenständigen getoptBefehls (der von MacOS verwendet wird). Dies unterstützt auch keine langen Optionen.

  • GNU-Implementierung von Standalone getopt. GNU getopt(3)(wird von der Befehlszeile getopt(1)unter Linux verwendet) unterstützt das Parsen langer Optionen.


Einige andere Antworten zeigen eine Lösung für die Verwendung der eingebauten Bash getopts, um lange Optionen nachzuahmen. Diese Lösung macht tatsächlich eine kurze Option, deren Charakter "-" ist. Sie erhalten also "-" als Flagge. Dann wird alles, was folgt, zu OPTARG, und Sie testen das OPTARG mit einem verschachtelten case.

Das ist klug, bringt aber einige Einschränkungen mit sich:

  • getoptskann die opt-Spezifikation nicht erzwingen. Es können keine Fehler zurückgegeben werden, wenn der Benutzer eine ungültige Option angibt. Sie müssen Ihre eigene Fehlerprüfung durchführen, während Sie OPTARG analysieren.
  • OPTARG wird für den langen Optionsnamen verwendet, was die Verwendung erschwert, wenn Ihre lange Option selbst ein Argument enthält. Sie müssen dies am Ende selbst als zusätzlichen Fall codieren.

Während es möglich ist, mehr Code zu schreiben, um die mangelnde Unterstützung für lange Optionen zu umgehen, ist dies viel mehr Arbeit und macht den Zweck der Verwendung eines getopt-Parsers zur Vereinfachung Ihres Codes teilweise zunichte.

Bill Karwin
quelle
17
Damit. Was ist die plattformübergreifende, tragbare Lösung?
Troelskn
6
GNU Getopt scheint die einzige Wahl zu sein. Installieren Sie auf einem Mac GNU getopt von macports. Unter Windows würde ich GNU getopt mit Cygwin installieren.
Bill Karwin
2
Offenbar , KSH getopts können lange Optionen behandeln.
Tgr
1
@ Bill +1, obwohl es auch ziemlich einfach ist, getopt aus dem Quellcode ( software.frodo.looijaard.name/getopt ) auf dem Mac zu erstellen . Sie können die auf Ihrem System installierte Version von getopt auch in Skripten mit "getopt -T; echo $?" Überprüfen.
Chinasaur
8
@ Bill Karwin: "Das eingebaute Bash-Getopts unterstützt keine langen Optionsnamen mit dem Double-Dash-Präfix." Getopts können jedoch durchgeführt werden, um lange Optionen zu unterstützen: siehe stackoverflow.com/a/7680682/915044 unten.
TomRoche
305

getoptund getoptssind verschiedene Tiere, und die Leute scheinen ein bisschen Missverständnis darüber zu haben, was sie tun. getoptsist ein integrierter Befehl zum bashVerarbeiten von Befehlszeilenoptionen in einer Schleife und zum Zuweisen jeder gefundenen Option und jedes gefundenen Werts zu integrierten Variablen, damit Sie sie weiter verarbeiten können. getoptEs handelt sich jedoch um ein externes Dienstprogramm, das Ihre Optionen für Sie nicht so verarbeitet, wie es beispielsweise Bash getopts, das Perl- GetoptModul oder Python optparse/ argparseModule tun. Alles, getoptwas Sie tun müssen, ist, die übergebenen Optionen zu kanonisieren - dh sie in ein Standardformular zu konvertieren, damit ein Shell-Skript sie leichter verarbeiten kann. Beispielsweise kann eine Anwendung von getoptFolgendes konvertieren:

myscript -ab infile.txt -ooutfile.txt

das sehr gut finden:

myscript -a -b -o outfile.txt infile.txt

Sie müssen die eigentliche Bearbeitung selbst vornehmen. Sie müssen überhaupt nicht verwenden, getoptwenn Sie verschiedene Einschränkungen für die Angabe von Optionen vornehmen:

  • Geben Sie nur eine Option pro Argument an.
  • Alle Optionen stehen vor Positionsparametern (dh Argumenten ohne Option).
  • Bei Optionen mit Werten (z. B. -ooben) muss der Wert als separates Argument (nach einem Leerzeichen) angegeben werden.

Warum getoptstatt verwenden getopts? Der Hauptgrund ist, dass nur GNU getoptIhnen Unterstützung für lange benannte Befehlszeilenoptionen bietet. 1 (GNU getoptist die Standardeinstellung unter Linux. Mac OS X und FreeBSD werden mit einer einfachen und nicht sehr nützlichen Version geliefertgetopt Version , aber die GNU-Version kann installiert werden. Siehe unten.)

Hier ist zum Beispiel ein Beispiel für die Verwendung von GNU getoptaus einem meiner Skripte mit dem Namen javawrap:

# NOTE: This requires GNU getopt.  On Mac OS X and FreeBSD, you have to install this
# separately; see below.
TEMP=`getopt -o vdm: --long verbose,debug,memory:,debugfile:,minheap:,maxheap: \
             -n 'javawrap' -- "$@"`

if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi

# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"

VERBOSE=false
DEBUG=false
MEMORY=
DEBUGFILE=
JAVA_MISC_OPT=
while true; do
  case "$1" in
    -v | --verbose ) VERBOSE=true; shift ;;
    -d | --debug ) DEBUG=true; shift ;;
    -m | --memory ) MEMORY="$2"; shift 2 ;;
    --debugfile ) DEBUGFILE="$2"; shift 2 ;;
    --minheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MinHeapFreeRatio=$2"; shift 2 ;;
    --maxheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MaxHeapFreeRatio=$2"; shift 2 ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

Auf diese Weise können Sie Optionen wie --verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt"oder ähnlich angeben . Der Aufruf von getoptbesteht darin, die Optionen zu kanonisieren, --verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt"damit Sie sie einfacher verarbeiten können. Das Zitieren um "$1"und "$2"ist wichtig, da es sicherstellt, dass Argumente mit Leerzeichen richtig behandelt werden.

Wenn Sie die ersten 9 Zeilen löschen (alles durch die eval setZeile), funktioniert der Code weiterhin ! Ihr Code ist jedoch viel wählerischer in Bezug auf die Arten von Optionen, die er akzeptiert: Insbesondere müssen Sie alle Optionen in der oben beschriebenen "kanonischen" Form angeben. Mit der Verwendung von getoptkönnen Sie jedoch Einzelbuchstabenoptionen gruppieren, kürzere, nicht mehrdeutige Formen von Langoptionen verwenden, entweder den --file foo.txtoder --file=foo.txt-Stil verwenden, entweder den -m 4096oder -m4096-Stil verwenden, Optionen und Nichtoptionen in beliebiger Reihenfolge mischen usw. getopt gibt auch eine Fehlermeldung aus, wenn nicht erkannte oder mehrdeutige Optionen gefunden werden.

HINWEIS : Es gibt tatsächlich zwei völlig unterschiedliche Versionen von getoptBasic getoptund GNU getoptmit unterschiedlichen Funktionen und unterschiedlichen Anrufkonventionen. 2 Basic getoptist ziemlich kaputt: Es verarbeitet nicht nur keine langen Optionen, sondern auch nicht einmal eingebettete Leerzeichen innerhalb von Argumenten oder leeren Argumenten, wohingegen getoptsdies richtig ist. Der obige Code funktioniert nicht in Basic getopt. GNU getoptwird standardmäßig unter Linux installiert, muss jedoch unter Mac OS X und FreeBSD separat installiert werden. Installieren Sie unter Mac OS X MacPorts ( http://www.macports.org ) und sudo port install getoptinstallieren Sie dann GNU getopt(normalerweise in /opt/local/bin) und stellen Sie sicher, dass /opt/local/binsich das in Ihrem Shell-Pfad befindet/usr/bin. Installieren Sie unter FreeBSD misc/getopt.

Eine Kurzanleitung zum Ändern des Beispielcodes für Ihr eigenes Programm: Von den ersten Zeilen ist alles "Boilerplate", das bis auf die aufrufende Zeile gleich bleiben sollte getopt. Sie sollten den Programmnamen nach ändern -n, kurze Optionen nach -ound lange Optionen nach angeben --long. Setzen Sie nach Optionen, die einen Wert annehmen, einen Doppelpunkt.

Wenn Sie schließlich Code sehen, der nur setanstelle von hat eval set, wurde er für BSD geschrieben getopt. Sie sollten es ändern, um den eval setStil zu verwenden, der mit beiden Versionen von gut funktioniert getopt, während die Ebene setmit GNU nicht richtig funktioniert getopt.

1 Eigentlich getoptsin ksh93Trägern lange genannte Optionen, aber diese Schale nicht so oft verwendet bash. In zshVerwenden Sie zparseoptsdiese Funktionalität zu erhalten.

2 Technisch gesehen ist "GNU getopt" eine Fehlbezeichnung. Diese Version wurde eher für Linux als für das GNU-Projekt geschrieben. Es folgt jedoch allen GNU-Konventionen, und der Begriff "GNU getopt" wird häufig verwendet (z. B. bei FreeBSD).

Urban Vagabond
quelle
3
Dies war sehr hilfreich. Die Idee, mit getopt die Optionen zu überprüfen und diese Optionen dann in einer sehr einfachen Schleife zu verarbeiten, funktionierte sehr gut, als ich einem Bash-Skript Optionen mit langem Stil hinzufügen wollte. Vielen Dank.
Ianmjones
2
getoptunter Linux ist kein GNU-Dienstprogramm und das traditionelle getoptkommt ursprünglich nicht von BSD, sondern von AT & T Unix. ksh93s getopts(ebenfalls von AT & T) unterstützen lange Optionen im GNU-Stil.
Stephane Chazelas
@StephaneChazelas - bearbeitet, um Ihre Kommentare wiederzugeben. Ich bevorzuge immer noch den Begriff "GNU getopt", obwohl er eine falsche Bezeichnung ist, da diese Version den GNU-Konventionen folgt und sich im Allgemeinen wie ein GNU-Programm POSIXLY_CORRECTverhält (z. B. unter Verwendung von ), während "Linux-erweitertes getopt" fälschlicherweise darauf hinweist, dass diese Version nur auf existiert Linux.
Urban Vagabond
1
Es stammt aus dem util-linux-Paket, ist also nur Linux, da dieses Softwarepaket nur für Linux gedacht ist (das getoptleicht auf andere Unices portiert werden könnte, aber viele andere Softwareprodukte util-linuxsind Linux-spezifisch). Alle Nicht-GNU-Programme, die GNU getopt (3) verwenden, verstehen $POSIX_CORRECT. Zum Beispiel würden Sie nicht sagen, dass dies aplaynur aus diesen Gründen GNU ist. Ich vermute, wenn FreeBSD GNU getopt erwähnt, bedeutet dies die GNU getopt (3) C-API.
Stephane Chazelas
@StephaneChazelas - FreeBSD hat eine Fehlermeldung "Build-Abhängigkeit: Bitte installieren Sie GNU getopt", die sich eindeutig auf das getoptutil bezieht , nicht auf getopt (3).
Urban Vagabond
202

Mit der in Bash integrierten Funktion getopts können Sie lange Optionen analysieren, indem Sie ein Bindestrichzeichen gefolgt von einem Doppelpunkt in die Option optspec einfügen:

#!/usr/bin/env bash 
optspec=":hv-:"
while getopts "$optspec" optchar; do
    case "${optchar}" in
        -)
            case "${OPTARG}" in
                loglevel)
                    val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                    echo "Parsing option: '--${OPTARG}', value: '${val}'" >&2;
                    ;;
                loglevel=*)
                    val=${OPTARG#*=}
                    opt=${OPTARG%=$val}
                    echo "Parsing option: '--${opt}', value: '${val}'" >&2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h)
            echo "usage: $0 [-v] [--loglevel[=]<value>]" >&2
            exit 2
            ;;
        v)
            echo "Parsing option: '-${optchar}'" >&2
            ;;
        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done

Nach dem Kopieren in den ausführbaren Dateinamen = getopts_test.shim aktuellen Arbeitsverzeichnis kann eine Ausgabe wie erzeugt werden

$ ./getopts_test.sh
$ ./getopts_test.sh -f
Non-option argument: '-f'
$ ./getopts_test.sh -h
usage: code/getopts_test.sh [-v] [--loglevel[=]<value>]
$ ./getopts_test.sh --help
$ ./getopts_test.sh -v
Parsing option: '-v'
$ ./getopts_test.sh --very-bad
$ ./getopts_test.sh --loglevel
Parsing option: '--loglevel', value: ''
$ ./getopts_test.sh --loglevel 11
Parsing option: '--loglevel', value: '11'
$ ./getopts_test.sh --loglevel=11
Parsing option: '--loglevel', value: '11'

Offensichtlich führt getopts weder eine OPTERRÜberprüfung noch eine Analyse von Optionsargumenten für die langen Optionen durch. Das obige Skriptfragment zeigt, wie dies manuell erfolgen kann. Das Grundprinzip funktioniert auch in der Debian Almquist-Shell ("dash"). Beachten Sie den Sonderfall:

getopts -- "-:"  ## without the option terminator "-- " bash complains about "-:"
getopts "-:"     ## this works in the Debian Almquist shell ("dash")

Beachten Sie, dass als GreyCat von über an dieser Trick http://mywiki.wooledge.org/BashFAQ , ein nicht standardmäßiges Verhalten der Shell ausnutzt, das das Optionsargument zulässt (dh den Dateinamen in "-f Dateiname"). mit der Option verkettet werden (wie in "-ffilename"). Der POSIX- Standard besagt, dass zwischen ihnen ein Leerzeichen stehen muss, was im Fall von "- longoption" die Optionsanalyse beenden und alle longoptions in Nichtoptionsargumente verwandeln würde.

Arvid angemessen
quelle
2
Eine Frage: Was ist die Semantik von !in val="${!OPTIND}?
TomRoche
2
@ TomRoche es ist indirekte Substitution: unix.stackexchange.com/a/41293/84316
ecbrodie
2
@ecbrodie: Das liegt daran, dass zwei Argumente tatsächlich verarbeitet wurden und nicht nur eines. Das erste Argument ist das Wort "loglevel", und das nächste ist das Argument zu diesem Argument. In der Zwischenzeit wird getoptsautomatisch nur OPTINDmit 1 erhöht , aber in unserem Fall muss es um 2 erhöht werden, also erhöhen wir es manuell um 1 und lassen getoptses dann für uns automatisch wieder um 1 erhöhen.
Victor Zamanian
3
Da wir uns hier mit Bash-Gleichgewicht befassen: Nackte Variablennamen sind in arithmetischen Ausdrücken zulässig, nicht $erforderlich. OPTIND=$(( $OPTIND + 1 ))kann gerecht sein OPTIND=$(( OPTIND + 1 )). Noch interessanter ist, dass Sie Variablen innerhalb eines arithmetischen Ausdrucks sogar zuweisen und erhöhen können, sodass Sie ihn weiter abkürzen : $(( ++OPTIND ))oder sogar (( ++OPTIND ))berücksichtigen können, dass er ++OPTINDimmer positiv ist, sodass ein Shell-Lauf mit dieser -eOption nicht ausgelöst wird. :-) gnu.org/software/bash/manual/html_node/Shell-Arithmetic.html
Clacke
3
Warum gibt es keine --very-badWarnung?
Tom Hale
148

Das eingebaute getopts Befehl AFAIK ist weiterhin nur auf Optionen mit nur einem Zeichen beschränkt.

Es gibt (oder gab es früher) ein externes Programm getopt, das eine Reihe von Optionen so neu organisiert, dass es einfacher zu analysieren ist. Sie können dieses Design auch an lange Optionen anpassen. Anwendungsbeispiel:

aflag=no
bflag=no
flist=""
set -- $(getopt abf: "$@")
while [ $# -gt 0 ]
do
    case "$1" in
    (-a) aflag=yes;;
    (-b) bflag=yes;;
    (-f) flist="$flist $2"; shift;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*)  break;;
    esac
    shift
done

# Process remaining non-option arguments
...

Sie können ein ähnliches Schema mit einem getoptlongBefehl verwenden.

Beachten Sie, dass die grundlegende Schwäche des externen getoptProgramms darin besteht, dass es schwierig ist, Argumente mit Leerzeichen zu behandeln und diese Leerzeichen genau beizubehalten. Aus diesem Grund ist das integrierte getoptsGerät überlegen, obwohl es nur durch die Verwendung von Einzelbuchstabenoptionen eingeschränkt ist.

Jonathan Leffler
quelle
11
getopt ist mit Ausnahme der GNU-Version (die eine andere Aufrufkonvention hat) grundlegend fehlerhaft. Benutze es nicht. Bitte verwenden Sie stattdessen ** getopts bash-hackers.org/wiki/doku.php/howto/getopts_tutorial
hendry
9
@hendry - von Ihrem eigenen Link: "Beachten Sie, dass getopts keine langen Optionen im GNU-Stil (--myoption) oder lange Optionen im XF86-Stil (-myoption) analysieren kann!"
Tom Auger
1
Jonathan - Sie sollten das Beispiel neu schreiben, um es eval setmit Anführungszeichen zu verwenden (siehe meine Antwort unten), damit es auch mit GNU getopt (der Standardeinstellung unter Linux) korrekt funktioniert und Leerzeichen korrekt behandelt.
Urban Vagabond
@ UrbanVagabond: Ich bin mir nicht sicher, warum ich das tun soll. Die Frage ist mit Unix gekennzeichnet, nicht mit Linux. Ich zeige absichtlich den traditionellen Mechanismus und er hat Probleme mit Leerzeichen in Argumenten usw. Sie können die moderne Linux-spezifische Version demonstrieren, wenn Sie dies wünschen, und Ihre Antwort tut dies. (Ich stelle fest, passim, dass Ihre Verwendung ${1+"$@"}kurios ist und im Widerspruch zu dem steht, was in modernen Shells und insbesondere zu jeder Shell unter Linux erforderlich ist. Siehe Verwenden von $ 1: + "$ @"} in / bin / sh für a Diskussion dieser Notation.)
Jonathan Leffler
Sie sollten es tun, weil eval setes sowohl mit GNU als auch mit BSD das Richtige tut getopt, während Plain setnur mit BSD das Richtige tut getopt. Sie können es also auch verwenden eval set, um Menschen zu ermutigen, sich daran zu gewöhnen. Übrigens, danke, ich wusste nicht, dass ${1+"$@"}das nicht mehr gebraucht wurde. Ich muss Dinge schreiben, die sowohl unter Mac OS X als auch unter Linux funktionieren - zwischen den beiden erzwingen sie viel Portabilität. Ich habe gerade überprüft und "$@"macht in der Tat , das Richtige auf alle sh, bash, ksh, und zshunter Mac OS X; sicherlich auch unter Linux.
Urban Vagabond
78

Hier ist ein Beispiel, das getopt mit langen Optionen verwendet:

aflag=no
bflag=no
cargument=none

# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc: -l along,blong,clong: -- "$@")
then
    # something went wrong, getopt will put out an error message for us
    exit 1
fi

set -- $options

while [ $# -gt 0 ]
do
    case $1 in
    -a|--along) aflag="yes" ;;
    -b|--blong) bflag="yes" ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="$2" ; shift;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done
sme
quelle
1
Sie sollten das Beispiel so umschreiben eval set, dass es mit Anführungszeichen verwendet wird (siehe meine Antwort unten), damit es auch mit GNU getopt (der Standardeinstellung unter Linux) korrekt funktioniert und Leerzeichen korrekt verarbeitet.
Urban Vagabond
1
Dies wird verwendet, getoptwährend die Frage ist getopts.
Niklas Berglund
Sind (--, (-*und (*gültige Muster? Wie unterscheiden sie sich von --, -*und *?
Maëlan
@ Maëlan - Die führende offene Klammer ist optional und daher (--)identisch mit --)einer caseZeilengruppe. Es ist seltsam, die ungleichmäßige Einrückung und inkonsistente Verwendung dieser optionalen führenden Parens zu sehen, aber der aktuelle Code der Antwort scheint mir gültig zu sein.
Adam Katz
59

Lange Optionen können vom getoptseingebauten Standard als "Argumente" für die -"Option" analysiert werden.

Dies ist eine tragbare und native POSIX-Shell - es werden keine externen Programme oder Bashismen benötigt.

Dieser Leitfaden implementiert lange Optionen als Argumente für die -Option, so --alphaist zu sehen , von getoptswie -mit dem Argument alphaund --bravo=foowird als gesehen -mit dem Argument bravo=foo. Das wahre Argument kann mit einem einfachen Ersatz geerntet werden : ${OPTARG#*=}.

In diesem Beispiel haben -bund -c(und ihre langen Formen --bravound --charlie) obligatorische Argumente. Argumente für lange Optionen kommen nach Gleichheitszeichen, z. B. --bravo=foo(Leerzeichen für lange Optionen wären schwer zu implementieren, siehe unten).

Da dies die getoptsintegrierte Lösung verwendet, unterstützt diese Lösung eine Verwendung wie cmd --bravo=foo -ac FILE(die Optionen kombiniert -aund -clange Optionen mit Standardoptionen verschachtelt), während die meisten anderen Antworten hier entweder Schwierigkeiten haben oder dies nicht tun.

die() { echo "$*" >&2; exit 2; }  # complain to STDERR and exit with error
needs_arg() { if [ -z "$OPTARG" ]; then die "No arg for --$OPT option"; fi; }

while getopts ab:c:-: OPT; do
  # support long options: https://stackoverflow.com/a/28466267/519360
  if [ "$OPT" = "-" ]; then   # long option: reformulate OPT and OPTARG
    OPT="${OPTARG%%=*}"       # extract long option name
    OPTARG="${OPTARG#$OPT}"   # extract long option argument (may be empty)
    OPTARG="${OPTARG#=}"      # if long option argument, remove assigning `=`
  fi
  case "$OPT" in
    a | alpha )    alpha=true ;;
    b | bravo )    needs_arg; bravo="$OPTARG" ;;
    c | charlie )  needs_arg; charlie="$OPTARG" ;;
    ??* )          die "Illegal option --$OPT" ;;  # bad long option
    \? )           exit 2 ;;  # bad short option (error reported via getopts)
  esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list

Wenn die Option ein Bindestrich ( -) ist, ist es eine lange Option. getoptswird die tatsächliche lange Option in $OPTARGzB --bravo=fooursprüngliche Sätze OPT='-'und analysiert haben OPTARG='bravo=foo'. Die ifZeilengruppe setzt $OPTauf den Inhalt von$OPTARG vor dem ersten Gleichheitszeichen ( bravoin unserem Beispiel) und entfernt dieses am Anfang von $OPTARG(ergibt =fooin diesem Schritt oder eine leere Zeichenfolge, wenn keine vorhanden ist =). Schließlich streifen wir die Argumentation ab =. Zu diesem Zeitpunkt $OPTist entweder eine kurze Option (ein Zeichen) oder eine lange Option (2+ Zeichen).

Das entspricht casedann entweder kurzen oder langen Optionen. getoptsBeschwert sich bei kurzen Optionen automatisch über Optionen und fehlende Argumente, sodass wir diese manuell mithilfe von replizieren müssenneeds_arg Funktion , die tödlich beendet wird, wenn sie $OPTARGleer ist. Die ??*Bedingung ?stimmt mit jeder verbleibenden langen Option überein ( entspricht einem einzelnen Zeichen und *entspricht null oder mehr, entspricht also ??*2+ Zeichen), sodass wir vor dem Beenden den Fehler "Unzulässige Option" ausgeben können.

(Ein Hinweis zu Variablennamen in Großbuchstaben: Im Allgemeinen wird empfohlen, Variablen in Großbuchstaben für die Systemnutzung zu reservieren. Ich $OPTbehalte sie in Großbuchstaben, um sie im Einklang zu halten $OPTARG, aber dies verstößt gegen diese Konvention. Ich denke, es passt, weil dies etwas ist, was das System hätte tun sollen, und es sollte sicher sein, weil es keine Standards (afaik) gibt, die eine solche Variable verwenden.)


Um sich über unerwartete Argumente für lange Optionen zu beschweren, ahmen Sie nach, was wir für obligatorische Argumente getan haben: Verwenden Sie eine Hilfsfunktion. Drehen Sie den Test einfach um, um sich über ein Argument zu beschweren, wenn eines nicht erwartet wird:

no_arg() { if [ -n "$OPTARG" ]; then die "No arg allowed for --$OPT option"; fi; }

Ein ältere Version dieser Antwort hatte den Versuch, lange Optionen mit durch Leerzeichen getrennten Argumenten zu akzeptieren, war jedoch nicht zuverlässig. getoptskönnte vorzeitig beendet werden, wenn angenommen wird, dass das Argument außerhalb seines Geltungsbereichs liegt und das manuelle Inkrementieren $OPTINDnicht in allen Shells funktioniert.

Dies würde unter Verwendung einer dieser Techniken erreicht werden:

und schloss dann mit so etwas [ $# -gt $OPTIND ] && OPTIND=$((OPTIND+1))

Adam Katz
quelle
Sehr schöne in sich geschlossene Lösung. Eine Frage: Da es letter-ckein Argument braucht, würde es nicht ausreichen, es zu verwenden letter-c)? das *scheint überflüssig.
Philip Kearns
1
@Arne Positionsargumente sind schlecht UX; Sie sind schwer zu verstehen und optionale Argumente sind ziemlich chaotisch. getoptsstoppt beim ersten Positionsargument, da es nicht dafür ausgelegt ist, mit ihnen umzugehen. Dies ermöglicht Unterbefehle mit eigenen Argumenten, z. B. git diff --colorwürde command --foo=moo bar --baz wazich --fooals Argument für commandund --baz wazals Argument (mit Option) für den barUnterbefehl interpretieren . Dies kann mit dem obigen Code erfolgen. Ich lehne ab, --bravo -blahweil --bravoein Argument erforderlich ist und es unklar ist, dass dies -blahkeine andere Option ist.
Adam Katz
1
Ich bin mit der UX nicht einverstanden: Positionsargumente sind nützlich und einfach, solange Sie ihre Anzahl begrenzen (auf höchstens 2 oder 1 plus N desselben Typs). Es sollte möglich sein, sie mit Schlüsselwortargumenten zu durchsetzen, da Benutzer dann Schritt für Schritt einen Befehl erstellen können (dh ls abc -la).
Arne Babenhauserheide
1
@AdamKatz: Ich habe einen kleinen Artikel dazu geschrieben: draketo.de/english/free-software/shell-argument-parsing - beinhaltet das wiederholte Lesen der verbleibenden Argumente, um nachfolgende Optionen abzufangen .
Arne Babenhauserheide
1
@ArneBabenhauserheide: Ich habe diese Antwort aktualisiert, um durch Leerzeichen getrennte Argumente zu unterstützen. Da dies evalin der POSIX-Shell erforderlich ist , wird es unter dem Rest der Antwort aufgeführt.
Adam Katz
33

Schauen Sie sich shFlags an , eine tragbare Shell-Bibliothek (dh: sh, bash, dash, ksh, zsh unter Linux, Solaris usw.).

Das Hinzufügen neuer Flags ist so einfach wie das Hinzufügen einer Zeile zu Ihrem Skript, und es bietet eine automatisch generierte Verwendungsfunktion.

Hier ist eine einfache Hello, world!Verwendung von shFlag :

#!/bin/sh

# source shflags from current directory
. ./shflags

# define a 'name' command-line string flag
DEFINE_string 'name' 'world' 'name to say hello to' 'n'

# parse the command-line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"

# say hello
echo "Hello, ${FLAGS_name}!"

Für Betriebssysteme mit dem erweiterten getopt, das lange Optionen unterstützt (z. B. Linux), können Sie Folgendes tun:

$ ./hello_world.sh --name Kate
Hello, Kate!

Im Übrigen müssen Sie die kurze Option verwenden:

$ ./hello_world.sh -n Kate
Hello, Kate!

Das Hinzufügen eines neuen Flags ist so einfach wie das Hinzufügen eines neuen DEFINE_ call.

k0pernikus
quelle
2
Das ist fantastisch, aber leider unterstützt mein getopt (OS X) keine Leerzeichen in Argumenten: Ich frage mich, ob es eine Alternative gibt.
Alastair Stuart
@AlastairStuart - Es gibt tatsächlich eine Alternative unter OS X. Verwenden Sie MacPorts, um GNU getopt zu installieren (es wird normalerweise in / opt / local / bin / getopt installiert).
Urban Vagabond
3
@UrbanVagabond - Die Installation von Nicht-System-Standardtools ist leider keine akzeptable Voraussetzung für ein ausreichend portables Tool.
Alastair Stuart
@AlastairStuart - siehe meine Antwort für eine tragbare Lösung, die die integrierten getopts anstelle von GNU getopt verwendet. Es ist dasselbe wie die grundlegende Verwendung von getopts, jedoch mit einer zusätzlichen Iteration für lange Optionen.
Adam Katz
31

Verwendung getoptsmit kurzen / langen Optionen und Argumenten


Funktioniert mit allen Kombinationen, zB:

  • foobar -f --bar
  • foobar --foo -b
  • foobar -bf --bar --foobar
  • foobar -fbFBAshorty --bar -FB --arguments = longhorn
  • foobar -fA "text shorty" -B --arguments = "text longhorn"
  • Bash Foobar -F - Barfoo
  • sh foobar -B --foobar - ...
  • bash ./foobar -F --bar

Einige Erklärungen für dieses Beispiel

Options=$@
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty

Wie die Nutzungsfunktion aussehen würde

function _usage() 
{
  ###### U S A G E : Help and ERROR ######
  cat <<EOF
   foobar $Options
  $*
          Usage: foobar <[options]>
          Options:
                  -b   --bar            Set bar to yes    ($foo)
                  -f   --foo            Set foo to yes    ($bart)
                  -h   --help           Show this message
                  -A   --arguments=...  Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
                  -B   --barfoo         Set barfoo to yes ($barfoo)
                  -F   --foobar         Set foobar to yes ($foobar)
EOF
}

[ $# = 0 ] && _usage "  >>>>>>>> no options given "

getops mit langen / kurzen Flags sowie langen Argumenten

while getopts ':bfh-A:BF' OPTION ; do
  case "$OPTION" in
    b  ) sbar=yes                       ;;
    f  ) sfoo=yes                       ;;
    h  ) _usage                         ;;   
    A  ) sarguments=yes;sARG="$OPTARG"  ;;
    B  ) sbarfoo=yes                    ;;
    F  ) sfoobar=yes                    ;;
    -  ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
         eval OPTION="\$$optind"
         OPTARG=$(echo $OPTION | cut -d'=' -f2)
         OPTION=$(echo $OPTION | cut -d'=' -f1)
         case $OPTION in
             --foo       ) lfoo=yes                       ;;
             --bar       ) lbar=yes                       ;;
             --foobar    ) lfoobar=yes                    ;;
             --barfoo    ) lbarfoo=yes                    ;;
             --help      ) _usage                         ;;
             --arguments ) larguments=yes;lARG="$OPTARG"  ;; 
             * )  _usage " Long: >>>>>>>> invalid options (long) " ;;
         esac
       OPTIND=1
       shift
      ;;
    ? )  _usage "Short: >>>>>>>> invalid options (short) "  ;;
  esac
done

Ausgabe

##################################################################
echo "----------------------------------------------------------"
echo "RESULT short-foo      : $sfoo                                    long-foo      : $lfoo"
echo "RESULT short-bar      : $sbar                                    long-bar      : $lbar"
echo "RESULT short-foobar   : $sfoobar                                 long-foobar   : $lfoobar"
echo "RESULT short-barfoo   : $sbarfoo                                 long-barfoo   : $lbarfoo"
echo "RESULT short-arguments: $sarguments  with Argument = \"$sARG\"   long-arguments: $larguments and $lARG"

Kombinieren Sie das Obige zu einem zusammenhängenden Skript

#!/bin/bash
# foobar: getopts with short and long options AND arguments

function _cleanup ()
{
  unset -f _usage _cleanup ; return 0
}

## Clear out nested functions on exit
trap _cleanup INT EXIT RETURN

###### some declarations for this example ######
Options=$@
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty

function _usage() 
{
  ###### U S A G E : Help and ERROR ######
  cat <<EOF
   foobar $Options
   $*
          Usage: foobar <[options]>
          Options:
                  -b   --bar            Set bar to yes    ($foo)
                    -f   --foo            Set foo to yes    ($bart)
                      -h   --help           Show this message
                  -A   --arguments=...  Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
                  -B   --barfoo         Set barfoo to yes ($barfoo)
                  -F   --foobar         Set foobar to yes ($foobar)
EOF
}

[ $# = 0 ] && _usage "  >>>>>>>> no options given "

##################################################################    
#######  "getopts" with: short options  AND  long options  #######
#######            AND  short/long arguments               #######
while getopts ':bfh-A:BF' OPTION ; do
  case "$OPTION" in
    b  ) sbar=yes                       ;;
    f  ) sfoo=yes                       ;;
    h  ) _usage                         ;;   
    A  ) sarguments=yes;sARG="$OPTARG"  ;;
    B  ) sbarfoo=yes                    ;;
    F  ) sfoobar=yes                    ;;
    -  ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
         eval OPTION="\$$optind"
         OPTARG=$(echo $OPTION | cut -d'=' -f2)
         OPTION=$(echo $OPTION | cut -d'=' -f1)
         case $OPTION in
             --foo       ) lfoo=yes                       ;;
             --bar       ) lbar=yes                       ;;
             --foobar    ) lfoobar=yes                    ;;
             --barfoo    ) lbarfoo=yes                    ;;
             --help      ) _usage                         ;;
               --arguments ) larguments=yes;lARG="$OPTARG"  ;; 
             * )  _usage " Long: >>>>>>>> invalid options (long) " ;;
         esac
       OPTIND=1
       shift
      ;;
    ? )  _usage "Short: >>>>>>>> invalid options (short) "  ;;
  esac
done
RapaNui
quelle
Funktioniert dies nicht mit mehr als einem langen Argument (-). Es scheint nur das erste für mich zu lesen.
Sinaesthetic
@Sinaesthetic - Ja, ich habe mit dem evalAnsatz für räumliche Argumente für lange Optionen gespielt und fand ihn bei bestimmten Shells unzuverlässig (obwohl ich davon ausgehe, dass er mit Bash funktioniert. In diesem Fall müssen Sie ihn nicht verwenden eval). In meiner Antwort erfahren Sie, wie Sie lange Optionsargumente akzeptieren, =und in meinen notierten Versuchen, Leerzeichen zu verwenden. Meine Lösung führt keine externen Anrufe durch, während diese cuteinige Male verwendet wird.
Adam Katz
24

Ein anderer Weg...

# translate long options to short
for arg
do
    delim=""
    case "$arg" in
       --help) args="${args}-h ";;
       --verbose) args="${args}-v ";;
       --config) args="${args}-c ";;
       # pass through anything else
       *) [[ "${arg:0:1}" == "-" ]] || delim="\""
           args="${args}${delim}${arg}${delim} ";;
    esac
done
# reset the translated args
eval set -- $args
# now we can process with getopt
while getopts ":hvc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        c)  source $OPTARG ;;
        \?) usage ;;
        :)
        echo "option -$OPTARG requires an argument"
        usage
        ;;
    esac
done
mtvee
quelle
1
Benötigt dies nicht bei jeder $argsNeuzuweisung ein Leerzeichen ? Dies könnte sogar ohne Bashismen geschehen, aber dieser Code verliert Leerzeichen in Optionen und Argumenten (ich glaube nicht, dass der $delimTrick funktionieren wird). Sie können stattdessen set innerhalb der forSchleife ausgeführt werden, wenn Sie vorsichtig genug sind, um sie nur bei der ersten Iteration zu leeren. Hier ist eine sicherere Version ohne Bashismen.
Adam Katz
18

Ich habe es irgendwie so gelöst:

# A string with command options
options=$@

# An array with all the arguments
arguments=($options)

# Loop index
index=0

for argument in $options
  do
    # Incrementing index
    index=`expr $index + 1`

    # The conditions
    case $argument in
      -a) echo "key $argument value ${arguments[index]}" ;;
      -abc) echo "key $argument value ${arguments[index]}" ;;
    esac
  done

exit;

Bin ich dumm oder so? getoptund getoptssind so verwirrend.

Benjamin
quelle
1
Dies scheint für mich zu funktionieren, ich weiß nicht, was das Problem mit dieser Methode ist, aber es scheint einfach zu sein, also muss es einen Grund geben, warum alle anderen es nicht verwenden.
Billy Moon
1
@ Billy Ja, das ist einfach, weil ich kein Skript zum Verwalten meiner Parameter usw. verwende. Grundsätzlich konvertiere ich die Argumentzeichenfolge ($ @) in ein Array und durchlaufe sie. In der Schleife ist der aktuelle Wert der Schlüssel und der nächste der Wert. So einfach ist das.
1
@ Theodore Ich bin froh, dass dies für Sie hilfreich war! Es war auch ein Schmerz für mich. Wenn Sie interessiert sind, können Sie hier ein Beispiel in Aktion sehen: raw.github.com/rafaelrinaldi/swf-to-html/master/swf-to-html.sh
2
Auf jeden Fall der einfachste Weg, den ich je gesehen habe. Ich habe es ein wenig geändert, indem ich i = $ (($ i + 1)) anstelle von expr verwendet habe, aber das Konzept ist luftdicht.
Thomas Dignan
6
Sie sind überhaupt nicht dumm, aber möglicherweise fehlt Ihnen eine Funktion: getopt (s) kann gemischte Optionen erkennen (z. B. -ltroder ). Darüber hinaus bietet es eine Fehlerbehandlung und eine einfache Möglichkeit, die behandelten Parameter nach Abschluss der Optionsbehandlung zu verschieben. -lt -r-l -t -r
Olivier Dulac
14

Wenn Sie die getoptAbhängigkeit nicht möchten , können Sie Folgendes tun:

while test $# -gt 0
do
  case $1 in

  # Normal option processing
    -h | --help)
      # usage and help
      ;;
    -v | --version)
      # version info
      ;;
  # ...

  # Special cases
    --)
      break
      ;;
    --*)
      # error unknown (long) option $1
      ;;
    -?)
      # error unknown (short) option $1
      ;;

  # FUN STUFF HERE:
  # Split apart combined short options
    -*)
      split=$1
      shift
      set -- $(echo "$split" | cut -c 2- | sed 's/./-& /g') "$@"
      continue
      ;;

  # Done with options
    *)
      break
      ;;
  esac

  # for testing purposes:
  echo "$1"

  shift
done

Dann können Sie natürlich keine langen Stiloptionen mit einem Bindestrich verwenden. Und wenn Sie verkürzte Versionen hinzufügen möchten (z. B. --verbos anstelle von --verbose), müssen Sie diese manuell hinzufügen.

Wenn Sie jedoch getoptsFunktionalität und lange Optionen suchen , ist dies eine einfache Möglichkeit.

Ich habe diesen Ausschnitt auch auf den Punkt gebracht .

jakesandlund
quelle
Dies scheint immer nur mit einer langen Option zu funktionieren, aber es hat meine Bedürfnisse erfüllt. Vielen Dank!
Kingjeffrey
Im Sonderfall --)scheint ein zu shift ;fehlen. Im Moment --bleibt das als erstes Argument ohne Option.
dgw
Ich denke, dass dies tatsächlich die bessere Antwort ist, obwohl, wie dgw hervorhebt, die --Option shiftdort drin sein muss . Ich sage, dies ist besser, weil die Alternativen entweder plattformabhängige Versionen von sind getoptoder getopts_longSie die Verwendung von Kurzoptionen erst zu Beginn des Befehls erzwingen müssen (dh Sie verwenden getoptsdann lange Optionen und verarbeiten sie anschließend), während dies eine beliebige Reihenfolge ergibt und vollständige Kontrolle.
Haravikk
Diese Antwort lässt mich fragen, warum wir einen Faden von Dutzenden von Antworten haben, um die Arbeit zu erledigen, die mit nichts anderem als dieser absolut klaren und unkomplizierten Lösung erledigt werden kann , und ob es einen Grund für die Milliarden getopt (s) Anwendungsfälle gibt, die nicht beweisen man selbst.
Florian Heigl
11

Der eingebaute getoptskann das nicht. Es gibt ein externes getopt (1) -Programm, das dies kann, aber Sie erhalten es nur unter Linux aus dem util-linux- Paket. Es wird mit einem Beispielskript getopt-parse.bash geliefert .

Es gibt auch eine getopts_longals Shell geschriebene Funktion.

Nietzche-jou
quelle
3
Das getoptwurde 1993 in FreeBSD Version 1.0 aufgenommen und ist seitdem Teil von FreeBSD. Als solches wurde es aus FreeBSD 4.x übernommen, um in das Darwin-Projekt von Apple aufgenommen zu werden. Ab OS X 10.6.8 bleibt die von Apple enthaltene Manpage ein genaues Duplikat der FreeBSD-Manpage. Also ja, es ist in OS X und Gobs anderer Betriebssysteme neben Linux enthalten. -1 zu dieser Antwort für die Fehlinformation.
Ghoti
8
#!/bin/bash
while getopts "abc:d:" flag
do
  case $flag in
    a) echo "[getopts:$OPTIND]==> -$flag";;
    b) echo "[getopts:$OPTIND]==> -$flag";;
    c) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
    d) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
  esac
done

shift $((OPTIND-1))
echo "[otheropts]==> $@"

exit

.

#!/bin/bash
until [ -z "$1" ]; do
  case $1 in
    "--dlong")
      shift
      if [ "${1:1:0}" != "-" ]
      then
        echo "==> dlong $1"
        shift
      fi;;
    *) echo "==> other $1"; shift;;
  esac
done
exit
3ED
quelle
2
Eine Erklärung wäre schön. Das erste Skript akzeptiert nur kurze Optionen, während das zweite Skript einen Fehler beim Parsen der langen Optionsargumente aufweist. Die Variable sollte "${1:0:1}"für Argument Nr. 1, Teilzeichenfolge bei Index 0, Länge 1, stehen. Dies erlaubt kein Mischen von kurzen und langen Optionen.
Adam Katz
7

In ksh93, getoptsunterstützt lange Namen ...

while getopts "f(file):s(server):" flag
do
    echo "$flag" $OPTIND $OPTARG
done

Zumindest haben die Tutorials, die ich gefunden habe, gesagt. Probieren Sie es aus und sehen Sie.

Richard Lynch
quelle
4
Dies ist das Getopts von ksh93. Abgesehen von dieser Syntax hat es auch eine kompliziertere Syntax, die auch lange Optionen ohne kurzes Äquivalent und mehr ermöglicht.
Jilles
2
Eine vernünftige Antwort. Das OP hat die WHAT-Shell nicht angegeben.
Ghoti
6

Ich schreibe nur ab und zu Shell-Skripte und falle aus der Praxis, daher ist jedes Feedback willkommen.

Bei Verwendung der von @Arvid Requate vorgeschlagenen Strategie haben wir einige Benutzerfehler festgestellt. Ein Benutzer, der vergisst, einen Wert anzugeben, wird versehentlich mit dem Namen der nächsten Option als Wert behandelt:

./getopts_test.sh --loglevel= --toc=TRUE

bewirkt, dass der Wert von "loglevel" als "--toc = TRUE" angezeigt wird. Dies kann vermieden werden.

Ich habe einige Ideen zum Überprüfen von Benutzerfehlern auf CLI unter http://mwiki.wooledge.org/BashFAQ/035 zur manuellen Analyse angepasst . Ich habe eine Fehlerprüfung in die Behandlung der Argumente "-" und "-" eingefügt.

Dann fing ich an, mit der Syntax herumzuspielen, daher sind alle Fehler hier ausschließlich meine Schuld, nicht die ursprünglichen Autoren.

Mein Ansatz hilft Benutzern, die es vorziehen, lange mit oder ohne Gleichheitszeichen einzugeben. Das heißt, es sollte dieselbe Antwort auf "--loglevel 9" haben wie "--loglevel = 9". Bei der - / space-Methode ist es nicht möglich, sicher zu wissen, ob der Benutzer ein Argument vergisst, sodass einige Vermutungen erforderlich sind.

  1. Wenn der Benutzer das Long / Equal-Vorzeichenformat (--opt =) hat, löst ein Leerzeichen nach = einen Fehler aus, da kein Argument angegeben wurde.
  2. Wenn der Benutzer Long / Space-Argumente (--opt) hat, verursacht dieses Skript einen Fehler, wenn kein Argument folgt (Befehlsende) oder wenn das Argument mit einem Bindestrich beginnt.

Wenn Sie damit beginnen, gibt es einen interessanten Unterschied zwischen den Formaten "--opt = value" und "--opt value". Mit dem Gleichheitszeichen wird das Befehlszeilenargument als "opt = value" und die zu behandelnde Arbeit als String-Analyse angesehen, um am "=" zu trennen. Im Gegensatz dazu lautet der Name des Arguments bei "--opt value" "opt" und wir haben die Herausforderung, den nächsten in der Befehlszeile angegebenen Wert zu erhalten. Hier verwendete @Arvid Requate $ {! OPTIND}, die indirekte Referenz. Ich verstehe das immer noch überhaupt nicht und Kommentare in BashFAQ scheinen vor diesem Stil zu warnen ( http://mywiki.wooledge.org/BashFAQ/006 ). Übrigens denke ich nicht, dass die Kommentare des vorherigen Posters zur Wichtigkeit von OPTIND = $ (($ OPTIND + 1)) korrekt sind. Ich will sagen,

In der neuesten Version dieses Skripts bedeutet Flag -v VERBOSE-Ausdruck.

Speichern Sie es in einer Datei mit dem Namen "cli-5.sh", machen Sie es ausführbar, und alles funktioniert oder schlägt auf die gewünschte Weise fehl

./cli-5.sh  -v --loglevel=44 --toc  TRUE
./cli-5.sh  -v --loglevel=44 --toc=TRUE
./cli-5.sh --loglevel 7
./cli-5.sh --loglevel=8
./cli-5.sh -l9

./cli-5.sh  --toc FALSE --loglevel=77
./cli-5.sh  --toc=FALSE --loglevel=77

./cli-5.sh   -l99 -t yyy
./cli-5.sh   -l 99 -t yyy

Hier ist eine Beispielausgabe der Fehlerprüfung auf dem Benutzer intpu

$ ./cli-5.sh  --toc --loglevel=77
ERROR: toc value must not have dash at beginning
$ ./cli-5.sh  --toc= --loglevel=77
ERROR: value for toc undefined

Sie sollten in Betracht ziehen, -v einzuschalten, da hier Interna von OPTIND und OPTARG gedruckt werden

#/usr/bin/env bash

## Paul Johnson
## 20171016
##

## Combines ideas from
## /programming/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
## by @Arvid Requate, and http://mwiki.wooledge.org/BashFAQ/035

# What I don't understand yet: 
# In @Arvid REquate's answer, we have 
# val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
# this works, but I don't understand it!


die() {
    printf '%s\n' "$1" >&2
    exit 1
}

printparse(){
    if [ ${VERBOSE} -gt 0 ]; then
        printf 'Parse: %s%s%s\n' "$1" "$2" "$3" >&2;
    fi
}

showme(){
    if [ ${VERBOSE} -gt 0 ]; then
        printf 'VERBOSE: %s\n' "$1" >&2;
    fi
}


VERBOSE=0
loglevel=0
toc="TRUE"

optspec=":vhl:t:-:"
while getopts "$optspec" OPTCHAR; do

    showme "OPTARG:  ${OPTARG[*]}"
    showme "OPTIND:  ${OPTIND[*]}"
    case "${OPTCHAR}" in
        -)
            case "${OPTARG}" in
                loglevel) #argument has no equal sign
                    opt=${OPTARG}
                    val="${!OPTIND}"
                    ## check value. If negative, assume user forgot value
                    showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
                    if [[ "$val" == -* ]]; then
                        die "ERROR: $opt value must not have dash at beginning"
                    fi
                    ## OPTIND=$(( $OPTIND + 1 )) # CAUTION! no effect?
                    printparse "--${OPTARG}" "  " "${val}"
                    loglevel="${val}"
                    shift
                    ;;
                loglevel=*) #argument has equal sign
                    opt=${OPTARG%=*}
                    val=${OPTARG#*=}
                    if [ "${OPTARG#*=}" ]; then
                        printparse "--${opt}" "=" "${val}"
                        loglevel="${val}"
                        ## shift CAUTION don't shift this, fails othewise
                    else
                        die "ERROR: $opt value must be supplied"
                    fi
                    ;;
                toc) #argument has no equal sign
                    opt=${OPTARG}
                    val="${!OPTIND}"
                    ## check value. If negative, assume user forgot value
                    showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
                    if [[ "$val" == -* ]]; then
                        die "ERROR: $opt value must not have dash at beginning"
                    fi
                    ## OPTIND=$(( $OPTIND + 1 )) #??
                    printparse "--${opt}" " " "${val}"
                    toc="${val}"
                    shift
                    ;;
                toc=*) #argument has equal sign
                    opt=${OPTARG%=*}
                    val=${OPTARG#*=}
                    if [ "${OPTARG#*=}" ]; then
                        toc=${val}
                        printparse "--$opt" " -> " "$toc"
                        ##shift ## NO! dont shift this
                    else
                        die "ERROR: value for $opt undefined"
                    fi
                    ;;

                help)
                    echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
                    exit 2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h|-\?|--help)
            ## must rewrite this for all of the arguments
            echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
            exit 2
            ;;
        l)
            loglevel=${OPTARG}
            printparse "-l" " "  "${loglevel}"
            ;;
        t)
            toc=${OPTARG}
            ;;
        v)
            VERBOSE=1
            ;;

        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done



echo "
After Parsing values
"
echo "loglevel  $loglevel" 
echo "toc  $toc"
pauljohn32
quelle
OPTIND=$(( $OPTIND + 1 )): wird benötigt, wenn Sie den Parameter von OPTIND 'verschlingen' (zum Beispiel: wenn einer verwendet wird --toc value : Wert in der Parameternummer $ OPTIND. Sobald Sie ihn für den Wert von toc abgerufen haben, sollten Sie getopts mitteilen, dass der nächste zu analysierende Parameter kein Wert ist. aber das danach (daher das:. OPTIND=$(( $OPTIND + 1 )) und Ihr Skript (sowie das Skript, auf das Sie sich beziehen) fehlt nach dem erledigt: shift $(( $OPTIND -1 ))(da getopts nach dem Parsen der Parameter 1 bis OPTIND-1 beendet wurden, müssen Sie sie so verschieben $@ist jetzt alle verbleibenden "Nicht-Optionen" -Parameter
Olivier Dulac
Oh, wenn Sie sich selbst verschieben, "verschieben" Sie die Parameter unter getopts, sodass OPTIND immer auf das Richtige zeigt ... aber ich finde es sehr verwirrend. Ich glaube (ich kann Ihr Skript momentan nicht testen), dass Sie nach der getopts while-Schleife immer noch die Verschiebung $ (($ OPTIND - 1)) benötigen, damit $ 1 jetzt nicht auf das ursprüngliche $ 1 (eine Option) verweist, sondern zu dem ersten der verbleibenden Argumente (die nach allen Optionen und ihren Werten kommen). Beispiel: myrm -foo -bar = baz thisarg thenthisone thenanother
Olivier Dulac
5

Eine weitere Version des Rades erfinden ...

Diese Funktion ist ein (hoffentlich) POSIX-kompatibler einfacher Bourne-Shell-Ersatz für GNU getopt. Es unterstützt kurze / lange Optionen, die obligatorische / optionale / keine Argumente akzeptieren können, und die Art und Weise, wie Optionen angegeben werden, ist fast identisch mit GNU getopt, sodass die Konvertierung trivial ist.

Natürlich ist dies immer noch ein beträchtlicher Teil des Codes, der in ein Skript eingefügt werden muss, aber es ist ungefähr die Hälfte der Zeilen der bekannten Shell-Funktion getopt_long und möglicherweise in Fällen vorzuziehen, in denen Sie nur vorhandene GNU-getopt-Verwendungen ersetzen möchten.

Dies ist ein ziemlich neuer Code, also YMMV (und lassen Sie mich auf jeden Fall wissen, ob dies aus irgendeinem Grund nicht POSIX-kompatibel ist - Portabilität war von Anfang an beabsichtigt, aber ich habe keine nützliche POSIX-Testumgebung).

Die Verwendung von Code und Beispielen folgt:

#!/bin/sh
# posix_getopt shell function
# Author: Phil S.
# Version: 1.0
# Created: 2016-07-05
# URL: http://stackoverflow.com/a/37087374/324105

# POSIX-compatible argument quoting and parameter save/restore
# http://www.etalabs.net/sh_tricks.html
# Usage:
# parameters=$(save "$@") # save the original parameters.
# eval "set -- ${parameters}" # restore the saved parameters.
save () {
    local param
    for param; do
        printf %s\\n "$param" \
            | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"
    done
    printf %s\\n " "
}

# Exit with status $1 after displaying error message $2.
exiterr () {
    printf %s\\n "$2" >&2
    exit $1
}

# POSIX-compatible command line option parsing.
# This function supports long options and optional arguments, and is
# a (largely-compatible) drop-in replacement for GNU getopt.
#
# Instead of:
# opts=$(getopt -o "$shortopts" -l "$longopts" -- "$@")
# eval set -- ${opts}
#
# We instead use:
# opts=$(posix_getopt "$shortopts" "$longopts" "$@")
# eval "set -- ${opts}"
posix_getopt () { # args: "$shortopts" "$longopts" "$@"
    local shortopts longopts \
          arg argtype getopt nonopt opt optchar optword suffix

    shortopts="$1"
    longopts="$2"
    shift 2

    getopt=
    nonopt=
    while [ $# -gt 0 ]; do
        opt=
        arg=
        argtype=
        case "$1" in
            # '--' means don't parse the remaining options
            ( -- ) {
                getopt="${getopt}$(save "$@")"
                shift $#
                break
            };;
            # process short option
            ( -[!-]* ) {         # -x[foo]
                suffix=${1#-?}   # foo
                opt=${1%$suffix} # -x
                optchar=${opt#-} # x
                case "${shortopts}" in
                    ( *${optchar}::* ) { # optional argument
                        argtype=optional
                        arg="${suffix}"
                        shift
                    };;
                    ( *${optchar}:* ) { # required argument
                        argtype=required
                        if [ -n "${suffix}" ]; then
                            arg="${suffix}"
                            shift
                        else
                            case "$2" in
                                ( -* ) exiterr 1 "$1 requires an argument";;
                                ( ?* ) arg="$2"; shift 2;;
                                (  * ) exiterr 1 "$1 requires an argument";;
                            esac
                        fi
                    };;
                    ( *${optchar}* ) { # no argument
                        argtype=none
                        arg=
                        shift
                        # Handle multiple no-argument parameters combined as
                        # -xyz instead of -x -y -z. If we have just shifted
                        # parameter -xyz, we now replace it with -yz (which
                        # will be processed in the next iteration).
                        if [ -n "${suffix}" ]; then
                            eval "set -- $(save "-${suffix}")$(save "$@")"
                        fi
                    };;
                    ( * ) exiterr 1 "Unknown option $1";;
                esac
            };;
            # process long option
            ( --?* ) {            # --xarg[=foo]
                suffix=${1#*=}    # foo (unless there was no =)
                if [ "${suffix}" = "$1" ]; then
                    suffix=
                fi
                opt=${1%=$suffix} # --xarg
                optword=${opt#--} # xarg
                case ",${longopts}," in
                    ( *,${optword}::,* ) { # optional argument
                        argtype=optional
                        arg="${suffix}"
                        shift
                    };;
                    ( *,${optword}:,* ) { # required argument
                        argtype=required
                        if [ -n "${suffix}" ]; then
                            arg="${suffix}"
                            shift
                        else
                            case "$2" in
                                ( -* ) exiterr 1 \
                                       "--${optword} requires an argument";;
                                ( ?* ) arg="$2"; shift 2;;
                                (  * ) exiterr 1 \
                                       "--${optword} requires an argument";;
                            esac
                        fi
                    };;
                    ( *,${optword},* ) { # no argument
                        if [ -n "${suffix}" ]; then
                            exiterr 1 "--${optword} does not take an argument"
                        fi
                        argtype=none
                        arg=
                        shift
                    };;
                    ( * ) exiterr 1 "Unknown option $1";;
                esac
            };;
            # any other parameters starting with -
            ( -* ) exiterr 1 "Unknown option $1";;
            # remember non-option parameters
            ( * ) nonopt="${nonopt}$(save "$1")"; shift;;
        esac

        if [ -n "${opt}" ]; then
            getopt="${getopt}$(save "$opt")"
            case "${argtype}" in
                ( optional|required ) {
                    getopt="${getopt}$(save "$arg")"
                };;
            esac
        fi
    done

    # Generate function output, suitable for:
    # eval "set -- $(posix_getopt ...)"
    printf %s "${getopt}"
    if [ -n "${nonopt}" ]; then
        printf %s "$(save "--")${nonopt}"
    fi
}

Anwendungsbeispiel:

# Process command line options
shortopts="hvd:c::s::L:D"
longopts="help,version,directory:,client::,server::,load:,delete"
#opts=$(getopt -o "$shortopts" -l "$longopts" -n "$(basename $0)" -- "$@")
opts=$(posix_getopt "$shortopts" "$longopts" "$@")
if [ $? -eq 0 ]; then
    #eval set -- ${opts}
    eval "set -- ${opts}"
    while [ $# -gt 0 ]; do
        case "$1" in
            ( --                ) shift; break;;
            ( -h|--help         ) help=1; shift; break;;
            ( -v|--version      ) version_help=1; shift; break;;
            ( -d|--directory    ) dir=$2; shift 2;;
            ( -c|--client       ) useclient=1; client=$2; shift 2;;
            ( -s|--server       ) startserver=1; server_name=$2; shift 2;;
            ( -L|--load         ) load=$2; shift 2;;
            ( -D|--delete       ) delete=1; shift;;
        esac
    done
else
    shorthelp=1 # getopt returned (and reported) an error.
fi
Phils
quelle
4

Die akzeptierte Antwort macht einen sehr guten Job und weist auf alle Mängel der eingebauten Bash hin getopts. Die Antwort endet mit:

Während es möglich ist, mehr Code zu schreiben, um die mangelnde Unterstützung für lange Optionen zu umgehen, ist dies viel mehr Arbeit und macht den Zweck der Verwendung eines getopt-Parsers zur Vereinfachung Ihres Codes teilweise zunichte.

Und obwohl ich dieser Aussage im Prinzip zustimme, bin ich der Meinung, dass die Häufigkeit, mit der wir alle diese Funktion in verschiedenen Skripten implementiert haben, es rechtfertigt, ein wenig Aufwand in die Erstellung einer "standardisierten", gut getesteten Lösung zu investieren.

Als solches habe ich die integrierte Bash "aktualisiert", getoptsindem ich sie getopts_longin einer reinen Bash ohne externe Abhängigkeiten implementiert habe . Die Verwendung der Funktion ist 100% kompatibel mit der eingebauten getopts.

Durch Einfügen getopts_long(das auf GitHub gehostet wird ) in ein Skript kann die Antwort auf die ursprüngliche Frage so einfach implementiert werden wie:

source "${PATH_TO}/getopts_long.bash"

while getopts_long ':c: copyfile:' OPTKEY; do
    case ${OPTKEY} in
        'c'|'copyfile')
            echo 'file supplied -- ${OPTARG}'
            ;;
        '?')
            echo "INVALID OPTION -- ${OPTARG}" >&2
            exit 1
            ;;
        ':')
            echo "MISSING ARGUMENT for option -- ${OPTARG}" >&2
            exit 1
            ;;
        *)
            echo "Misconfigured OPTSPEC or uncaught option -- ${OPTKEY}" >&2
            exit 1
            ;;
    esac
done

shift $(( OPTIND - 1 ))
[[ "${1}" == "--" ]] && shift
UrsaDK
quelle
3

Ich habe noch nicht genug Repräsentanten, um seine Lösung zu kommentieren oder abzustimmen, aber die Antwort von sme hat für mich sehr gut funktioniert. Das einzige Problem, auf das ich stieß, war, dass die Argumente in einfache Anführungszeichen gesetzt wurden (also habe ich sie entfernt).

Ich habe auch einige Beispielverwendungen und Hilfetext hinzugefügt. Ich werde meine leicht erweiterte Version hier einfügen:

#!/bin/bash

# getopt example
# from: /programming/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
HELP_TEXT=\
"   USAGE:\n
    Accepts - and -- flags, can specify options that require a value, and can be in any order. A double-hyphen (--) will stop processing options.\n\n

    Accepts the following forms:\n\n

    getopt-example.sh -a -b -c value-for-c some-arg\n
    getopt-example.sh -c value-for-c -a -b some-arg\n
    getopt-example.sh -abc some-arg\n
    getopt-example.sh --along --blong --clong value-for-c -a -b -c some-arg\n
    getopt-example.sh some-arg --clong value-for-c\n
    getopt-example.sh
"

aflag=false
bflag=false
cargument=""

# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc:h\? -l along,blong,help,clong: -- "$@")
then
    # something went wrong, getopt will put out an error message for us
    exit 1
fi

set -- $options

while [ $# -gt 0 ]
do
    case $1 in
    -a|--along) aflag=true ;;
    -b|--blong) bflag=true ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="$2" ; shift;;
    -h|--help|-\?) echo -e $HELP_TEXT; exit;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done

# to remove the single quotes around arguments, pipe the output into:
# | sed -e "s/^'\\|'$//g"  (just leading/trailing) or | sed -e "s/'//g"  (all)

echo aflag=${aflag}
echo bflag=${bflag}
echo cargument=${cargument}

while [ $# -gt 0 ]
do
    echo arg=$1
    shift

    if [[ $aflag == true ]]; then
        echo a is true
    fi

done
kghastie
quelle
3

Hier finden Sie einige verschiedene Ansätze für das Parsen komplexer Optionen in Bash: http://mywiki.wooledge.org/ComplexOptionParsing

Ich habe das folgende erstellt, und ich denke, es ist gut, weil es nur minimalen Code enthält und sowohl lange als auch kurze Optionen funktionieren. Eine lange Option kann bei diesem Ansatz auch mehrere Argumente haben.

#!/bin/bash
# Uses bash extensions.  Not portable as written.

declare -A longoptspec
longoptspec=( [loglevel]=1 ) #use associative array to declare how many arguments a long option expects, in this case we declare that loglevel expects/has one argument, long options that aren't listed i n this way will have zero arguments by default
optspec=":h-:"
while getopts "$optspec" opt; do
while true; do
    case "${opt}" in
        -) #OPTARG is name-of-long-option or name-of-long-option=value
            if [[ "${OPTARG}" =~ .*=.* ]] #with this --key=value format only one argument is possible
            then
                opt=${OPTARG/=*/}
                OPTARG=${OPTARG#*=}
                ((OPTIND--))    
            else #with this --key value1 value2 format multiple arguments are possible
                opt="$OPTARG"
                OPTARG=(${@:OPTIND:$((longoptspec[$opt]))})
            fi
            ((OPTIND+=longoptspec[$opt]))
            continue #now that opt/OPTARG are set we can process them as if getopts would've given us long options
            ;;
        loglevel)
          loglevel=$OPTARG
            ;;
        h|help)
            echo "usage: $0 [--loglevel[=]<value>]" >&2
            exit 2
            ;;
    esac
break; done
done

# End of file
user3573558
quelle
2

Ich habe ziemlich lange an diesem Thema gearbeitet ... und meine eigene Bibliothek erstellt, die Sie in Ihrem Hauptskript finden müssen. Ein Beispiel finden Sie unter libopt4shell und cd2mpc . Ich hoffe es hilft !

Liealgebra
quelle
2

Eine verbesserte Lösung:

# translate long options to short
# Note: This enable long options but disable "--?*" in $OPTARG, or disable long options after  "--" in option fields.
for ((i=1;$#;i++)) ; do
    case "$1" in
        --)
            # [ ${args[$((i-1))]} == ... ] || EndOpt=1 ;;& # DIRTY: we still can handle some execptions...
            EndOpt=1 ;;&
        --version) ((EndOpt)) && args[$i]="$1"  || args[$i]="-V";;
        # default case : short option use the first char of the long option:
        --?*) ((EndOpt)) && args[$i]="$1"  || args[$i]="-${1:2:1}";;
        # pass through anything else:
        *) args[$i]="$1" ;;
    esac
    shift
done
# reset the translated args
set -- "${args[@]}"

function usage {
echo "Usage: $0 [options] files" >&2
    exit $1
}

# now we can process with getopt
while getopts ":hvVc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        V)  echo $Version ; exit ;;
        c)  source $OPTARG ;;
        \?) echo "unrecognized option: -$opt" ; usage -1 ;;
        :)
        echo "option -$OPTARG requires an argument"
        usage -1
        ;;
    esac
done

shift $((OPTIND-1))
[[ "$1" == "--" ]] && shift
jbar
quelle
2

Vielleicht ist es einfacher, ksh zu verwenden, nur für den getopts-Teil, wenn lange Befehlszeilenoptionen benötigt werden, da dies dort einfacher möglich ist.

# Working Getopts Long => KSH

#! /bin/ksh
# Getopts Long
USAGE="s(showconfig)"
USAGE+="c:(createdb)"
USAGE+="l:(createlistener)"
USAGE+="g:(generatescripts)"
USAGE+="r:(removedb)"
USAGE+="x:(removelistener)"
USAGE+="t:(createtemplate)"
USAGE+="h(help)"

while getopts "$USAGE" optchar ; do
    case $optchar in
    s)  echo "Displaying Configuration" ;;
        c)  echo "Creating Database $OPTARG" ;;
    l)  echo "Creating Listener LISTENER_$OPTARG" ;;
    g)  echo "Generating Scripts for Database $OPTARG" ;;
    r)  echo "Removing Database $OPTARG" ;;
    x)  echo "Removing Listener LISTENER_$OPTARG" ;;
    t)  echo "Creating Database Template" ;;
    h)  echo "Help" ;;
    esac
done
efstathiou_e
quelle
+1 - Beachten Sie, dass dies auf ksh93 beschränkt ist - aus dem Open Source AST-Projekt (AT & T Research).
Henk Langeveld
2

Ich wollte etwas ohne externe Abhängigkeiten mit strikter Bash-Unterstützung (-u), und ich brauchte es, um auch mit den älteren Bash-Versionen zu arbeiten. Dies behandelt verschiedene Arten von Parametern:

  • kurze bools (-h)
  • kurze Optionen (-i "image.jpg")
  • lange bools (--help)
  • entspricht Optionen (--file = "filename.ext")
  • Leerzeichenoptionen (--file "filename.ext")
  • konkatinierte Bools (-hvm)

Fügen Sie einfach oben in Ihrem Skript Folgendes ein:

# Check if a list of params contains a specific param
# usage: if _param_variant "h|?|help p|path f|file long-thing t|test-thing" "file" ; then ...
# the global variable $key is updated to the long notation (last entry in the pipe delineated list, if applicable)
_param_variant() {
  for param in $1 ; do
    local variants=${param//\|/ }
    for variant in $variants ; do
      if [[ "$variant" = "$2" ]] ; then
        # Update the key to match the long version
        local arr=(${param//\|/ })
        let last=${#arr[@]}-1
        key="${arr[$last]}"
        return 0
      fi
    done
  done
  return 1
}

# Get input parameters in short or long notation, with no dependencies beyond bash
# usage:
#     # First, set your defaults
#     param_help=false
#     param_path="."
#     param_file=false
#     param_image=false
#     param_image_lossy=true
#     # Define allowed parameters
#     allowed_params="h|?|help p|path f|file i|image image-lossy"
#     # Get parameters from the arguments provided
#     _get_params $*
#
# Parameters will be converted into safe variable names like:
#     param_help,
#     param_path,
#     param_file,
#     param_image,
#     param_image_lossy
#
# Parameters without a value like "-h" or "--help" will be treated as
# boolean, and will be set as param_help=true
#
# Parameters can accept values in the various typical ways:
#     -i "path/goes/here"
#     --image "path/goes/here"
#     --image="path/goes/here"
#     --image=path/goes/here
# These would all result in effectively the same thing:
#     param_image="path/goes/here"
#
# Concatinated short parameters (boolean) are also supported
#     -vhm is the same as -v -h -m
_get_params(){

  local param_pair
  local key
  local value
  local shift_count

  while : ; do
    # Ensure we have a valid param. Allows this to work even in -u mode.
    if [[ $# == 0 || -z $1 ]] ; then
      break
    fi

    # Split the argument if it contains "="
    param_pair=(${1//=/ })
    # Remove preceeding dashes
    key="${param_pair[0]#--}"

    # Check for concatinated boolean short parameters.
    local nodash="${key#-}"
    local breakout=false
    if [[ "$nodash" != "$key" && ${#nodash} -gt 1 ]]; then
      # Extrapolate multiple boolean keys in single dash notation. ie. "-vmh" should translate to: "-v -m -h"
      local short_param_count=${#nodash}
      let new_arg_count=$#+$short_param_count-1
      local new_args=""
      # $str_pos is the current position in the short param string $nodash
      for (( str_pos=0; str_pos<new_arg_count; str_pos++ )); do
        # The first character becomes the current key
        if [ $str_pos -eq 0 ] ; then
          key="${nodash:$str_pos:1}"
          breakout=true
        fi
        # $arg_pos is the current position in the constructed arguments list
        let arg_pos=$str_pos+1
        if [ $arg_pos -gt $short_param_count ] ; then
          # handle other arguments
          let orignal_arg_number=$arg_pos-$short_param_count+1
          local new_arg="${!orignal_arg_number}"
        else
          # break out our one argument into new ones
          local new_arg="-${nodash:$str_pos:1}"
        fi
        new_args="$new_args \"$new_arg\""
      done
      # remove the preceding space and set the new arguments
      eval set -- "${new_args# }"
    fi
    if ! $breakout ; then
      key="$nodash"
    fi

    # By default we expect to shift one argument at a time
    shift_count=1
    if [ "${#param_pair[@]}" -gt "1" ] ; then
      # This is a param with equals notation
      value="${param_pair[1]}"
    else
      # This is either a boolean param and there is no value,
      # or the value is the next command line argument
      # Assume the value is a boolean true, unless the next argument is found to be a value.
      value=true
      if [[ $# -gt 1 && -n "$2" ]]; then
        local nodash="${2#-}"
        if [ "$nodash" = "$2" ]; then
          # The next argument has NO preceding dash so it is a value
          value="$2"
          shift_count=2
        fi
      fi
    fi

    # Check that the param being passed is one of the allowed params
    if _param_variant "$allowed_params" "$key" ; then
      # --key-name will now become param_key_name
      eval param_${key//-/_}="$value"
    else
      printf 'WARNING: Unknown option (ignored): %s\n' "$1" >&2
    fi
    shift $shift_count
  done
}

Und benutze es so:

# Assign defaults for parameters
param_help=false
param_path=$(pwd)
param_file=false
param_image=true
param_image_lossy=true
param_image_lossy_quality=85

# Define the params we will allow
allowed_params="h|?|help p|path f|file i|image image-lossy image-lossy-quality"

# Get the params from arguments provided
_get_params $*
Heath Dutton
quelle
1

Um plattformübergreifend kompatibel zu bleiben und die Abhängigkeit von externen ausführbaren Dateien zu vermeiden, habe ich Code aus einer anderen Sprache portiert.

Ich finde es sehr einfach zu bedienen, hier ein Beispiel:

ArgParser::addArg "[h]elp"    false    "This list"
ArgParser::addArg "[q]uiet"   false    "Supress output"
ArgParser::addArg "[s]leep"   1        "Seconds to sleep"
ArgParser::addArg "v"         1        "Verbose mode"

ArgParser::parse "$@"

ArgParser::isset help && ArgParser::showArgs

ArgParser::isset "quiet" \
   && echo "Quiet!" \
   || echo "Noisy!"

local __sleep
ArgParser::tryAndGetArg sleep into __sleep \
   && echo "Sleep for $__sleep seconds" \
   || echo "No value passed for sleep"

# This way is often more convienient, but is a little slower
echo "Sleep set to: $( ArgParser::getArg sleep )"

Das erforderliche BASH ist etwas länger als es sein könnte, aber ich wollte vermeiden, dass ich mich auf die assoziativen Arrays von BASH 4 verlasse. Sie können dies auch direkt von http://nt4.com/bash/argparser.inc.sh herunterladen

#!/usr/bin/env bash

# Updates to this script may be found at
# http://nt4.com/bash/argparser.inc.sh

# Example of runtime usage:
# mnc.sh --nc -q Caprica.S0*mkv *.avi *.mp3 --more-options here --host centos8.host.com

# Example of use in script (see bottom)
# Just include this file in yours, or use
# source argparser.inc.sh

unset EXPLODED
declare -a EXPLODED
function explode 
{
    local c=$# 
    (( c < 2 )) && 
    {
        echo function "$0" is missing parameters 
        return 1
    }

    local delimiter="$1"
    local string="$2"
    local limit=${3-99}

    local tmp_delim=$'\x07'
    local delin=${string//$delimiter/$tmp_delim}
    local oldifs="$IFS"

    IFS="$tmp_delim"
    EXPLODED=($delin)
    IFS="$oldifs"
}


# See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
# Usage: local "$1" && upvar $1 "value(s)"
upvar() {
    if unset -v "$1"; then           # Unset & validate varname
        if (( $# == 2 )); then
            eval $1=\"\$2\"          # Return single value
        else
            eval $1=\(\"\${@:2}\"\)  # Return array
        fi
    fi
}

function decho
{
    :
}

function ArgParser::check
{
    __args=${#__argparser__arglist[@]}
    for (( i=0; i<__args; i++ ))
    do
        matched=0
        explode "|" "${__argparser__arglist[$i]}"
        if [ "${#1}" -eq 1 ]
        then
            if [ "${1}" == "${EXPLODED[0]}" ]
            then
                decho "Matched $1 with ${EXPLODED[0]}"
                matched=1

                break
            fi
        else
            if [ "${1}" == "${EXPLODED[1]}" ]
            then
                decho "Matched $1 with ${EXPLODED[1]}"
                matched=1

                break
            fi
        fi
    done
    (( matched == 0 )) && return 2
    # decho "Key $key has default argument of ${EXPLODED[3]}"
    if [ "${EXPLODED[3]}" == "false" ]
    then
        return 0
    else
        return 1
    fi
}

function ArgParser::set
{
    key=$3
    value="${1:-true}"
    declare -g __argpassed__$key="$value"
}

function ArgParser::parse
{

    unset __argparser__argv
    __argparser__argv=()
    # echo parsing: "$@"

    while [ -n "$1" ]
    do
        # echo "Processing $1"
        if [ "${1:0:2}" == '--' ]
        then
            key=${1:2}
            value=$2
        elif [ "${1:0:1}" == '-' ]
        then
            key=${1:1}               # Strip off leading -
            value=$2
        else
            decho "Not argument or option: '$1'" >& 2
            __argparser__argv+=( "$1" )
            shift
            continue
        fi
        # parameter=${tmp%%=*}     # Extract name.
        # value=${tmp##*=}         # Extract value.
        decho "Key: '$key', value: '$value'"
        # eval $parameter=$value
        ArgParser::check $key
        el=$?
        # echo "Check returned $el for $key"
        [ $el -eq  2 ] && decho "No match for option '$1'" >&2 # && __argparser__argv+=( "$1" )
        [ $el -eq  0 ] && decho "Matched option '${EXPLODED[2]}' with no arguments"        >&2 && ArgParser::set true "${EXPLODED[@]}"
        [ $el -eq  1 ] && decho "Matched option '${EXPLODED[2]}' with an argument of '$2'"   >&2 && ArgParser::set "$2" "${EXPLODED[@]}" && shift
        shift
    done
}

function ArgParser::isset
{
    declare -p "__argpassed__$1" > /dev/null 2>&1 && return 0
    return 1
}

function ArgParser::getArg
{
    # This one would be a bit silly, since we can only return non-integer arguments ineffeciently
    varname="__argpassed__$1"
    echo "${!varname}"
}

##
# usage: tryAndGetArg <argname> into <varname>
# returns: 0 on success, 1 on failure
function ArgParser::tryAndGetArg
{
    local __varname="__argpassed__$1"
    local __value="${!__varname}"
    test -z "$__value" && return 1
    local "$3" && upvar $3 "$__value"
    return 0
}

function ArgParser::__construct
{
    unset __argparser__arglist
    # declare -a __argparser__arglist
}

##
# @brief add command line argument
# @param 1 short and/or long, eg: [s]hort
# @param 2 default value
# @param 3 description
##
function ArgParser::addArg
{
    # check for short arg within long arg
    if [[ "$1" =~ \[(.)\] ]]
    then
        short=${BASH_REMATCH[1]}
        long=${1/\[$short\]/$short}
    else
        long=$1
    fi
    if [ "${#long}" -eq 1 ]
    then
        short=$long
        long=''
    fi
    decho short: "$short"
    decho long: "$long"
    __argparser__arglist+=("$short|$long|$1|$2|$3")
}

## 
# @brief show available command line arguments
##
function ArgParser::showArgs
{
    # declare -p | grep argparser
    printf "Usage: %s [OPTION...]\n\n" "$( basename "${BASH_SOURCE[0]}" )"
    printf "Defaults for the options are specified in brackets.\n\n";

    __args=${#__argparser__arglist[@]}
    for (( i=0; i<__args; i++ ))
    do
        local shortname=
        local fullname=
        local default=
        local description=
        local comma=

        explode "|" "${__argparser__arglist[$i]}"

        shortname="${EXPLODED[0]:+-${EXPLODED[0]}}" # String Substitution Guide: 
        fullname="${EXPLODED[1]:+--${EXPLODED[1]}}" # http://tldp.org/LDP/abs/html/parameter-substitution.html
        test -n "$shortname" \
            && test -n "$fullname" \
            && comma=","

        default="${EXPLODED[3]}"
        case $default in
            false )
                default=
                ;;
            "" )
                default=
                ;;
            * )
                default="[$default]"
        esac

        description="${EXPLODED[4]}"

        printf "  %2s%1s %-19s %s %s\n" "$shortname" "$comma" "$fullname" "$description" "$default"
    done
}

function ArgParser::test
{
    # Arguments with a default of 'false' do not take paramaters (note: default
    # values are not applied in this release)

    ArgParser::addArg "[h]elp"      false       "This list"
    ArgParser::addArg "[q]uiet" false       "Supress output"
    ArgParser::addArg "[s]leep" 1           "Seconds to sleep"
    ArgParser::addArg "v"           1           "Verbose mode"

    ArgParser::parse "$@"

    ArgParser::isset help && ArgParser::showArgs

    ArgParser::isset "quiet" \
        && echo "Quiet!" \
        || echo "Noisy!"

    local __sleep
    ArgParser::tryAndGetArg sleep into __sleep \
        && echo "Sleep for $__sleep seconds" \
        || echo "No value passed for sleep"

    # This way is often more convienient, but is a little slower
    echo "Sleep set to: $( ArgParser::getArg sleep )"

    echo "Remaining command line: ${__argparser__argv[@]}"

}

if [ "$( basename "$0" )" == "argparser.inc.sh" ]
then
    ArgParser::test "$@"
fi
Orwellophil
quelle
1

Wenn alle Ihre langen Optionen eindeutige und übereinstimmende erste Zeichen als kurze Optionen haben, so zum Beispiel

./slamm --chaos 23 --plenty test -quiet

Ist das gleiche wie

./slamm -c 23 -p test -q

Sie können dies vor getopts verwenden, um $ args neu zu schreiben:

# change long options to short options

for arg; do 
    [[ "${arg:0:1}" == "-" ]] && delim="" || delim="\""
    if [ "${arg:0:2}" == "--" ]; 
       then args="${args} -${arg:2:1}" 
       else args="${args} ${delim}${arg}${delim}"
    fi
done

# reset the incoming args
eval set -- $args

# proceed as usual
while getopts ":b:la:h" OPTION; do
    .....

Danke für mtvee für die Inspiration ;-)

Commonpike
quelle
Ich verstehe die Bedeutung von eval hier nicht
benutzerfreundlich
1

Wenn dies einfach der Fall ist, möchten Sie das Skript aufrufen

myscript.sh --input1 "ABC" --input2 "PQR" --input2 "XYZ"

Dann können Sie diesen einfachsten Weg gehen, um ihn mit Hilfe von getopt und --longoptions zu erreichen

Versuchen Sie dies, hoffen Sie, dass dies nützlich ist

# Read command line options
ARGUMENT_LIST=(
    "input1"
    "input2"
    "input3"
)



# read arguments
opts=$(getopt \
    --longoptions "$(printf "%s:," "${ARGUMENT_LIST[@]}")" \
    --name "$(basename "$0")" \
    --options "" \
    -- "$@"
)


echo $opts

eval set --$opts

while true; do
    case "$1" in
    --input1)  
        shift
        empId=$1
        ;;
    --input2)  
        shift
        fromDate=$1
        ;;
    --input3)  
        shift
        toDate=$1
        ;;
      --)
        shift
        break
        ;;
    esac
    shift
done
Ashish Shetkar
quelle
0

getopts "könnte verwendet werden", um lange Optionen zu analysieren, solange Sie nicht erwarten, dass sie Argumente enthalten ...

So geht's:

$ cat > longopt
while getopts 'e:-:' OPT; do
  case $OPT in
    e) echo echo: $OPTARG;;
    -) #long option
       case $OPTARG in
         long-option) echo long option;;
         *) echo long option: $OPTARG;;
       esac;;
  esac
done

$ bash longopt -e asd --long-option --long1 --long2 -e test
echo: asd
long option
long option: long1
long option: long2
echo: test

Wenn Sie versuchen, OPTIND zum Abrufen eines Parameters für die Option long zu verwenden, behandelt getopts diesen als ersten nicht optionalen Positionsparameter und beendet das Parsen anderer Parameter. In einem solchen Fall ist es besser, wenn Sie es manuell mit einer einfachen case-Anweisung behandeln.

Dies wird "immer" funktionieren:

$ cat >longopt2
while (($#)); do
    OPT=$1
    shift
    case $OPT in
        --*) case ${OPT:2} in
            long1) echo long1 option;;
            complex) echo comples with argument $1; shift;;
        esac;;

        -*) case ${OPT:1} in
            a) echo short option a;;
            b) echo short option b with parameter $1; shift;;
        esac;;
    esac
done


$ bash longopt2 --complex abc -a --long -b test
comples with argument abc
short option a
short option b with parameter test

Es ist jedoch nicht so flexibel wie getopts und Sie müssen einen Großteil des Fehlerprüfcodes selbst in den Fallinstanzen ausführen ...

Aber es ist eine Option.

estani
quelle
Aber lange Optionen erwarten oft Argumente. Und Sie könnten mehr mit dem tun - damit es funktioniert, obwohl es so etwas wie ein Hack ist. Am Ende könnte man argumentieren, dass, wenn es es nicht nativ unterstützt, jede Art der Implementierung so etwas wie ein Hack ist, aber man könnte trotzdem das - auch erweitern. Und ja, Shift ist sehr nützlich, aber wenn es ein Argument erwartet, kann es natürlich dazu führen, dass das nächste Argument (wenn keines vom Benutzer angegeben wird) Teil des erwarteten Arguments ist.
Pryftan
Ja, dies ist ein POC für lange Argumente ohne Argumente. Um zwischen den beiden zu unterscheiden, benötigen Sie eine Konfiguration wie getops. Und was die Verschiebung betrifft, können Sie sie jederzeit mit dem Set "zurückstellen". In jedem Fall muss es konfigurierbar sein, ob ein Parameter erwartet wird oder nicht. Sie könnten sogar etwas Magie dafür verwenden, aber dann werden Sie die Benutzer dazu zwingen - um zu signalisieren, dass die Magie stoppt und die Positionsparameter beginnen, was imho schlimmer ist.
Estani
Meinetwegen. Das ist mehr als vernünftig. Ich erinnere mich nicht einmal daran, was ich vorhatte, und ich hatte diese Frage selbst völlig vergessen. Ich erinnere mich nur vage, wie ich es überhaupt gefunden habe. Prost. Oh und habe eine +1 für die Idee. Sie haben sich die Mühe gemacht und auch geklärt, worauf Sie hinaus wollten. Ich respektiere Leute, die sich die Mühe machen, anderen Ideen usw. zu geben
Pryftan
0

Integriertgetopts nur kurze Optionen analysieren (außer in ksh93), aber Sie können immer noch einige Skriptzeilen hinzufügen, damit getopts lange Optionen verarbeitet.

Hier ist ein Teil des Codes unter http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts

  #== set short options ==#
SCRIPT_OPTS=':fbF:B:-:h'
  #== set long options associated with short one ==#
typeset -A ARRAY_OPTS
ARRAY_OPTS=(
    [foo]=f
    [bar]=b
    [foobar]=F
    [barfoo]=B
    [help]=h
    [man]=h
)

  #== parse options ==#
while getopts ${SCRIPT_OPTS} OPTION ; do
    #== translate long options to short ==#
    if [[ "x$OPTION" == "x-" ]]; then
        LONG_OPTION=$OPTARG
        LONG_OPTARG=$(echo $LONG_OPTION | grep "=" | cut -d'=' -f2)
        LONG_OPTIND=-1
        [[ "x$LONG_OPTARG" = "x" ]] && LONG_OPTIND=$OPTIND || LONG_OPTION=$(echo $OPTARG | cut -d'=' -f1)
        [[ $LONG_OPTIND -ne -1 ]] && eval LONG_OPTARG="\$$LONG_OPTIND"
        OPTION=${ARRAY_OPTS[$LONG_OPTION]}
        [[ "x$OPTION" = "x" ]] &&  OPTION="?" OPTARG="-$LONG_OPTION"

        if [[ $( echo "${SCRIPT_OPTS}" | grep -c "${OPTION}:" ) -eq 1 ]]; then
            if [[ "x${LONG_OPTARG}" = "x" ]] || [[ "${LONG_OPTARG}" = -* ]]; then 
                OPTION=":" OPTARG="-$LONG_OPTION"
            else
                OPTARG="$LONG_OPTARG";
                if [[ $LONG_OPTIND -ne -1 ]]; then
                    [[ $OPTIND -le $Optnum ]] && OPTIND=$(( $OPTIND+1 ))
                    shift $OPTIND
                    OPTIND=1
                fi
            fi
        fi
    fi

    #== options follow by another option instead of argument ==#
    if [[ "x${OPTION}" != "x:" ]] && [[ "x${OPTION}" != "x?" ]] && [[ "${OPTARG}" = -* ]]; then 
        OPTARG="$OPTION" OPTION=":"
    fi

    #== manage options ==#
    case "$OPTION" in
        f  ) foo=1 bar=0                    ;;
        b  ) foo=0 bar=1                    ;;
        B  ) barfoo=${OPTARG}               ;;
        F  ) foobar=1 && foobar_name=${OPTARG} ;;
        h ) usagefull && exit 0 ;;
        : ) echo "${SCRIPT_NAME}: -$OPTARG: option requires an argument" >&2 && usage >&2 && exit 99 ;;
        ? ) echo "${SCRIPT_NAME}: -$OPTARG: unknown option" >&2 && usage >&2 && exit 99 ;;
    esac
done
shift $((${OPTIND} - 1))

Hier ist ein Test:

# Short options test
$ ./foobar_any_getopts.sh -bF "Hello world" -B 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello world
files=file1 file2

# Long and short options test
$ ./foobar_any_getopts.sh --bar -F Hello --barfoo 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello
files=file1 file2

Andernfalls kann Korn Shell ksh93 in letzter Zeitgetopts natürlich lange Optionen analysieren und sogar eine Manpage gleichermaßen anzeigen. (Siehe http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options )

Michel VONGVILAY uxora.com
quelle
0

Das integrierte OS X (BSD) getopt unterstützt keine langen Optionen, die GNU-Version jedoch : brew install gnu-getopt. Dann etwas Ähnliches wie : cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt.

wprl
quelle
0

EasyOptions verarbeitet kurze und lange Optionen:

## Options:
##   --verbose, -v   Verbose mode
##   --logfile=NAME  Log filename

source easyoptions || exit

if test -n "${verbose}"; then
    echo "log file: ${logfile}"
    echo "arguments: ${arguments[@]}"
fi
Renato Silva
quelle