552 Stimmen

Verwendung von getopts zur Verarbeitung langer und kurzer Befehlszeilenoptionen

Ich möchte Lang- und Kurzformen von Befehlszeilenoptionen haben, die über mein Shell-Skript aufgerufen werden.

Ich weiß, dass getopts verwendet werden, aber wie in Perl konnte ich das nicht mit der Shell machen.

Irgendwelche Ideen, wie dies geschehen kann, damit ich Optionen wie:

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

Im obigen Beispiel bedeuten beide Befehle für meine Shell das Gleiche, aber mit getopts Ich habe es nicht geschafft, diese zu implementieren?

442voto

Urban Vagabond Punkte 6696

getopt y getopts sind unterschiedliche Wesen, und die Menschen scheinen ihre Aufgaben nicht richtig zu verstehen. getopts ist ein eingebauter Befehl zur bash um Befehlszeilenoptionen in einer Schleife zu verarbeiten und jede gefundene Option und jeden gefundenen Wert der Reihe nach eingebauten Variablen zuzuweisen, damit Sie sie weiter verarbeiten können. getopt ist jedoch ein externes Hilfsprogramm, und es nicht wirklich Ihre Optionen für Sie bearbeitet die Art und Weise, wie z.B. bash getopts die Perl Getopt Modul oder das Python optparse / argparse Module tun. All das getopt ist es, die übergebenen Optionen zu kanonisieren - d.h. sie in eine Standardform umzuwandeln, so dass es für ein Shell-Skript einfacher ist, sie zu verarbeiten. Zum Beispiel kann eine Anwendung von getopt könnte das Folgende umwandeln:

myscript -ab infile.txt -ooutfile.txt

in diese:

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

Die eigentliche Bearbeitung müssen Sie selbst vornehmen. Sie müssen nicht getopt überhaupt nicht, wenn Sie verschiedene Einschränkungen bei der Angabe von Optionen vornehmen:

  • nur eine Option pro Argument angeben;
  • alle Optionen stehen vor allen Positionsparametern (d.h. Nicht-Options-Argumenten);
  • für Optionen mit Werten (z. B. -o oben), muss der Wert als separates Argument (nach einem Leerzeichen) angegeben werden.

Warum verwenden getopt 代わりに getopts ? Der Hauptgrund ist, dass nur GNU getopt bietet Ihnen Unterstützung für lang benannte Befehlszeilenoptionen. 1 (GNU getopt ist die Standardeinstellung unter Linux. Mac OS X und FreeBSD werden mit einem einfachen und nicht sehr nützlichen getopt aber die GNU-Version kann installiert werden; siehe unten).

Hier zum Beispiel ein Beispiel für die Verwendung von GNU getopt aus einem Skript von mir namens 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 angeben wie --verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt" oder ähnlich. Die Wirkung des Aufrufs zu getopt ist die Kanonisierung der Optionen auf --verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt" damit Sie sie leichter bearbeiten können. Die Quotierung um "$1" y "$2" ist wichtig, da sie sicherstellt, dass Argumente mit Leerzeichen richtig behandelt werden.

Wenn Sie die ersten 9 Zeilen löschen (alles bis zur eval set Zeile), wird der Code noch funktionieren ! Allerdings wird Ihr Code viel wählerischer sein, was die akzeptierten Optionen angeht: Insbesondere müssen Sie alle Optionen in der oben beschriebenen "kanonischen" Form angeben. Mit der Verwendung von getopt können Sie jedoch Optionen mit einzelnen Buchstaben gruppieren, kürzere, nicht eindeutige Formen von langen Optionen verwenden, entweder die --file foo.txt o --file=foo.txt Stil, verwenden Sie entweder die -m 4096 o -m4096 Stil, Mischung von Optionen und Nicht-Optionen in beliebiger Reihenfolge, usw. getopt gibt auch eine Fehlermeldung aus, wenn nicht erkannte oder mehrdeutige Optionen gefunden werden.

ANMERKUNG : Es gibt eigentlich zwei völlig unterschiedlich Versionen von getopt , grundlegend getopt und GNU getopt mit unterschiedlichen Funktionen und unterschiedlichen Anrufkonventionen. 2 Grundlegend getopt ist ziemlich kaputt: Er kann nicht nur keine langen Optionen verarbeiten, sondern auch keine eingebetteten Leerzeichen innerhalb von Argumenten oder leere Argumente, während getopts macht das richtig. Der obige Code wird in Basic nicht funktionieren getopt . GNU getopt ist unter Linux standardmäßig installiert, unter Mac OS X und FreeBSD muss es jedoch separat installiert werden. Unter Mac OS X installieren Sie MacPorts ( http://www.macports.org ) und tun dann sudo port install getopt zur Installation von GNU getopt (normalerweise in /opt/local/bin ), und stellen Sie sicher, dass /opt/local/bin ist in Ihrem Shell-Pfad vor /usr/bin . Unter FreeBSD installieren Sie misc/getopt .

Eine Kurzanleitung zum Ändern des Beispielcodes für Ihr eigenes Programm: Die ersten paar Zeilen sind allesamt "Standardtexte", die unverändert bleiben sollten, mit Ausnahme der Zeile, die Folgendes aufruft getopt . Sie sollten den Programmnamen ändern, nachdem Sie -n geben Sie kurze Optionen nach -o und lange Optionen nach --long . Setzen Sie einen Doppelpunkt nach Optionen, die einen Wert annehmen.

Schließlich, wenn Sie Code sehen, der gerade set 代わりに eval set Es wurde für BSD geschrieben. getopt . Sie sollten es so ändern, dass Sie die eval set Stil, der mit beiden Versionen von getopt , während die Ebene set funktioniert nicht richtig mit GNU getopt .

1 Eigentlich, getopts en ksh93 unterstützt lang benannte Optionen, aber diese Shell wird nicht so oft benutzt wie bash . Unter zsh verwenden zparseopts um diese Funktionalität zu erhalten.

2 Technisch gesehen, "GNU getopt " ist eine falsche Bezeichnung; diese Version wurde eigentlich für Linux und nicht für das GNU-Projekt geschrieben. Sie folgt jedoch allen GNU-Konventionen, und der Begriff "GNU getopt " wird üblicherweise verwendet (z.B. unter FreeBSD).

357voto

Bill Karwin Punkte 493880

Es gibt drei Umsetzungen, die in Frage kommen:

  • Bash-Einrichtung getopts . Dies unterstützt keine langen Optionsnamen mit dem Präfix "Doppelstrich". Es werden nur einstellige Optionen unterstützt.

  • BSD UNIX-Implementierung von Standalone getopt Befehl (der von MacOS verwendet wird). Auch hier werden keine langen Optionen unterstützt.

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


Einige andere Antworten zeigen eine Lösung für die Verwendung des Bash-Builtins getopts um Long-Optionen zu imitieren. Diese Lösung erzeugt eine kurze Option, deren Zeichen "-" ist. Sie erhalten also "--" als Flagge. Dann wird alles, was darauf folgt, zu OPTARG, und man testet das OPTARG mit einer verschachtelten case .

Das ist klug, hat aber auch seine Tücken:

  • getopts kann die opt spec nicht durchsetzen. Es kann keine Fehler zurückgeben, wenn der Benutzer eine ungültige Option angibt. Sie müssen Ihre eigene Fehlerprüfung durchführen, wenn Sie OPTARG parsen.
  • OPTARG wird für den langen Optionsnamen verwendet, was die Verwendung erschwert, wenn Ihre lange Option selbst ein Argument hat. Sie müssen dies dann selbst als zusätzlichen Fall kodieren.

Es ist zwar möglich, mehr Code zu schreiben, um die fehlende Unterstützung für lange Optionen zu umgehen, aber das ist viel mehr Arbeit und verfehlt teilweise den Zweck der Verwendung eines getopt-Parsers zur Vereinfachung Ihres Codes.

254voto

Arvid Requate Punkte 2427

Die in der Bash eingebaute Funktion getopts kann verwendet werden, um lange Optionen zu analysieren, indem ein Bindestrich gefolgt von einem Doppelpunkt in die optspec eingefügt wird:

#!/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 die ausführbare Datei name= getopts_test.sh im aktuelles Arbeitsverzeichnis kann man eine Ausgabe erzeugen wie

$ ./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 OPTERR Prüfung noch das Parsen von Optionsargumenten für die langen Optionen. Das obige Skriptfragment zeigt, wie dies manuell durchgeführt werden 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, wie GreyCat von drüben bei http://mywiki.wooledge.org/BashFAQ weist darauf hin, dass dieser Trick ein nicht standardisiertes Verhalten der Shell ausnutzt, das es erlaubt, das Optionsargument (d.h. den Dateinamen in "-f Dateiname") mit der Option (wie in "-ffilename") zu verketten. Die POSIX Standard besagt, dass zwischen ihnen ein Leerzeichen stehen muss, was im Fall von "-- longoption" das Option-Parsing beenden und alle longoptions in Nicht-Options-Argumente verwandeln würde.

173voto

Adam Katz Punkte 12112

Lange Optionen können mit dem Standard getopts als "Argumente" für die - "Option"

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

Diese Anleitung implementiert lange Optionen als Argumente für die - Option, also --alpha wird gesehen von getopts als - mit Argument alpha y --bravo=foo wird gesehen als - mit Argument bravo=foo . Das wahre Argument kann mit einer einfachen Ersetzung gewonnen werden: ${OPTARG#*=} .

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

Da dies die getopts eingebautes unterstützt diese Lösung Verwendungen wie cmd --bravo=foo -ac FILE (mit den kombinierten Optionen -a y -c und verschachtelt lange Optionen mit Standardoptionen), während die meisten anderen Antworten hier entweder Mühe 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. getopts hat die eigentliche lange Option in $OPTARG z.B. --bravo=foo ursprünglich setzt OPT='-' y OPTARG='bravo=foo' . Die if Strophenfolgen $OPT auf den Inhalt von $OPTARG vor dem ersten Gleichheitszeichen ( bravo in unserem Beispiel) und entfernt diese dann vom Anfang der $OPTARG (mit dem Ergebnis =foo in diesem Schritt oder eine leere Zeichenfolge, wenn es keine = ). Schließlich entfernen wir den führenden Teil des Arguments = . Zu diesem Zeitpunkt, $OPT ist entweder eine kurze Option (ein Zeichen) oder eine lange Option (2+ Zeichen).

El case entspricht dann entweder kurzen oder langen Optionen. Für kurze Optionen, getopts beschwert sich automatisch über Optionen und fehlende Argumente, so dass wir diese manuell mit der needs_arg Funktion, die fatalerweise beendet wird, wenn $OPTARG leer ist. Die Website ??* Bedingung wird auf jede verbleibende lange Option ( ? passt auf ein einzelnes Zeichen und * stimmt mit null oder mehr überein, also ??* 2+ Zeichen übereinstimmt), so dass wir die Fehlermeldung "Illegale Option" ausgeben können, bevor wir das Programm beenden.


Kleiner Fehler: Wenn jemand eine ungültige einstellige lange Option angibt (und es ist nicht auch eine kurze Option), wird dies mit einem Fehler, aber ohne Meldung beendet. Das liegt daran, dass diese Implementierung davon ausgeht, dass es sich um eine kurze Option handelt. Man könnte das mit einer zusätzlichen Variable in der Stanza für die lange Option verfolgen, die der case und dann im Endzustand darauf testen, aber ich halte das für einen zu großen Eckfall, um mir die Mühe zu machen.

Variablennamen in Großbuchstaben: Im Allgemeinen wird empfohlen, Variablen in Großbuchstaben für die Verwendung im System zu reservieren. Ich behalte $OPT als reine Großbuchstaben, um sie mit $OPTARG aber dies ist ein Bruch mit dieser Konvention. Ich denke, es passt, weil dies etwas ist, was das System hätte tun sollen, und es sollte sicher sein; ich habe noch von keinem Standard gehört, der diesen Variablennamen verwendet.

Sich über unerwartete Argumente für lange Optionen zu beschweren: Mimik needs_arg mit einem umgedrehten Test, um sich über einen Streit zu beschweren, wenn keiner erwartet wird:

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

Um lange Optionen mit durch Leerzeichen getrennten Argumenten zu akzeptieren: Sie können das nächste Argument mit eval "ARG_B=\"\$$OPTIND\"" (oder mit Bashs indirekte Ausweitung , ARG_B="${!OPTIND}" ) und dann inkrementieren $OPTIND wie in einem ältere Version dieser Antwort aber sie ist unzuverlässig; getopts kann vorzeitig mit der Annahme beendet werden, dass das Argument außerhalb seines Geltungsbereichs lag, und einige Implementierungen vertragen keine manuellen Manipulationen von $OPTIND .

148voto

Jonathan Leffler Punkte 694013

Die eingebaute getopts ist, AFAIK, immer noch auf Optionen mit nur einem Zeichen beschränkt.

Es gibt (oder gab) ein externes Programm getopt die eine Reihe von Optionen so reorganisieren würde, dass sie leichter zu analysieren sind. Sie könnten dieses Design auch für lange Optionen anpassen. Beispiel für die Verwendung:

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önnten ein ähnliches Schema mit einer getoptlong comando.

Beachten Sie, dass die grundlegende Schwäche der externen getopt Programm ist die Schwierigkeit, Argumente mit Leerzeichen zu behandeln und diese Leerzeichen genau zu erhalten. Aus diesem Grund ist die eingebaute getopts ist besser, wenn auch eingeschränkt durch die Tatsache, dass es nur Ein-Buchstaben-Optionen verarbeitet.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X