it-swarm.com.de

Verwenden von getopts im bash-Shell-Skript, um lange und kurze Befehlszeilenoptionen zu erhalten

Ich möchte lange und kurze Formen von Befehlszeilenoptionen mit meinem Shell-Skript aufrufen.

Ich weiß, dass getopts verwendet werden kann, aber wie in Perl konnte ich mit Shell nicht dasselbe tun.

Irgendwelche Ideen, wie dies möglich ist, so dass ich Optionen wie verwenden kann:

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

In der obigen Beschreibung bedeuten beide Befehle dasselbe für meine Shell, aber mit getopts konnte ich diese nicht implementieren.

362
gagneet

Es gibt drei Implementierungen, die berücksichtigt werden können:

  • Bash Builtin getopts. Lange Optionsnamen mit dem Doppelstrich-Präfix werden nicht unterstützt. Es werden nur Optionen für einzelne Zeichen unterstützt.

  • BSD-UNIX-Implementierung des Standalone-Befehls getopt (verwendet MacOS). 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 die Analyse langer Optionen. 


Einige andere Antworten zeigen eine Lösung für die Verwendung des bash Builtin getopts, um lange Optionen nachzuahmen. Diese Lösung macht eigentlich eine kurze Option, deren Zeichen "-" ist. Sie erhalten also "-" als Flagge. Dann wird alles, was danach folgt, zu OPTARG und Sie testen das OPTARG mit einem verschachtelten case.

Das ist klug, aber es gibt einige Vorbehalte: 

  • getopts kann die Auswahloption 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, wenn Sie OPTARG analysieren.
  • OPTARG wird für den Namen der langen Option verwendet, was die Verwendung erschwert, wenn Ihre lange Option ein Argument enthält. Am Ende müssen Sie das selbst als zusätzlichen Fall kodieren.

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

268
Bill Karwin

getopt und getopts sind verschiedene Bestien, und die Leute scheinen ein wenig Missverständnis darüber zu haben, was sie tun. getopts ist ein integrierter Befehl für bash, um Befehlszeilenoptionen in einer Schleife zu verarbeiten und jede gefundene Option und jeden Wert den eingebauten Variablen zuzuweisen, damit Sie sie weiter verarbeiten können. getopt ist jedoch ein externes Hilfsprogramm, und verarbeitet Ihre Optionen nicht wirklich für Sie, wie es z. bash getopts, das Perl Getopt-Modul oder die Python optparseargparse-Module. Alles, was getopt tut, ist, die übergebenen Optionen zu kanonisieren - d. H. Sie in ein Standardformat umzuwandeln, sodass ein Shell-Skript sie leichter verarbeiten kann. Eine Anwendung von getopt könnte beispielsweise Folgendes 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 getopt nicht verwenden, wenn Sie verschiedene Einschränkungen für die Art und Weise festlegen, in der Sie Optionen angeben können: 

  • setzen Sie nur eine Option pro Argument. 
  • alle Optionen stehen vor allen Positionsparametern (d. h. Argumenten ohne Option).
  • bei Optionen mit Werten (z. B. -o oben) muss der Wert als separates Argument (nach einem Leerzeichen) angegeben werden.

Warum getopt anstelle von getopts verwenden? Der Hauptgrund ist, dass nur mit GNU getopt Sie Befehlszeilenoptionen mit einem langen Namen unterstützen.1 (GNU getopt ist der Standard unter Linux. Mac OS X und FreeBSD enthalten ein einfaches und nicht sehr nützliches getopt, aber die Version GNU kann installiert werden. Siehe unten.)

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' -- "[email protected]"`

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 ähnliches angeben. Der Aufruf von getopt hat die Wirkung, die Optionen auf --verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt" zu kanonisieren, damit Sie sie einfacher verarbeiten können. Die Anführungszeichen um "$1" und "$2" sind wichtig, da sie sicherstellen, dass Argumente mit Leerzeichen ordnungsgemäß verarbeitet werden.

Wenn Sie die ersten 9 Zeilen löschen (alles durch die eval set-Zeile), wird der Code _/noch funktionieren! Ihr Code wird jedoch in Bezug auf die von ihm akzeptierten Optionen viel wählerischer sein: Insbesondere müssen Sie alle Optionen in der oben beschriebenen "kanonischen" Form angeben. Mit getopt können Sie jedoch Optionen für einen Buchstaben gruppieren, kürzere, nicht mehrdeutige Formen von Langoptionen verwenden. Verwenden Sie entweder den Stil --file foo.txt oder --file=foo.txt. Verwenden Sie entweder den Stil -m 4096 oder -m4096 sowie Mischoptionen und Nicht-Optionen jede Reihenfolge usw. getopt gibt auch eine Fehlermeldung aus, wenn nicht erkannte oder mehrdeutige Optionen gefunden werden.

NOTE: Es gibt tatsächlich zwei total verschiedene Versionen von getopt, basic getopt und GNU getopt mit unterschiedlichen Funktionen und unterschiedlichen Aufrufkonventionen.2 Basic getopt ist ziemlich kaputt: Es behandelt nicht nur lange Optionen, es kann auch nicht einmal eingebettete Leerzeichen in Argumenten oder leeren Argumenten behandeln, wohingegen getopts dies richtig macht. Der obige Code funktioniert nicht in Basic getopt. GNU getopt wird standardmäßig unter Linux installiert, unter Mac OS X und FreeBSD muss es jedoch separat installiert werden. Installieren Sie unter Mac OS X MacPorts ( http://www.macports.org ) und führen Sie dann Sudo port install getopt aus, um GNU getopt (normalerweise in /opt/local/bin) zu installieren, und stellen Sie sicher, dass sich /opt/local/bin in Ihrem Shell-Pfad befindet vor /usr/bin. Installieren Sie unter FreeBSD misc/getopt.

Eine kurze Anleitung zum Ändern des Beispielcodes für Ihr eigenes Programm: Von den ersten Zeilen ist alles "boilerplate", das sollte gleich bleiben, mit Ausnahme der Zeile, die getopt aufruft. Sie sollten den Programmnamen nach -n ändern, kurze Optionen nach -o und lange Optionen nach --long angeben. Setzen Sie nach Optionen, die einen Wert annehmen, einen Doppelpunkt.

Wenn Sie Code sehen, der nur set anstelle von eval set enthält, wurde er für BSD getopt geschrieben. Sie sollten es ändern, um den eval set-Stil zu verwenden, der mit beiden Versionen von getopt gut funktioniert, während der einfache set mit GNU getopt nicht richtig funktioniert.

1Tatsächlich unterstützt getopts in ksh93 Optionen mit lange Namen, aber diese Shell wird nicht so oft wie bash verwendet. Verwenden Sie in zshzparseopts, um diese Funktionalität zu erhalten.

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

275
Urban Vagabond

Mit der eingebauten Getopts-Funktion von Bash können Sie lange Optionen analysieren, indem Sie ein Bindestrichzeichen gefolgt von einem Doppelpunkt in das optspec setzen:

#!/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 aktuellen Arbeitsverzeichnis kann man eine Ausgabe wie erzeugen

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

Natürlich führt getopts weder die OPTERR-Prüfung noch das Parsen von Optionen mit Argumenten für die langen Optionen durch. Das obige Skriptfragment zeigt, wie dies manuell erfolgen kann. Das Grundprinzip funktioniert auch in der Debian-Almquist-Shell ("Bindestrich"). 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 unter http://mywiki.wooledge.org/BashFAQ ausführt, dieser Trick ein nicht standardmäßiges Verhalten der Shell ausnutzt, das das Optionsargument (dh den Dateinamen in "- f dateiname "), die mit der Option verkettet werden soll (wie in" -ffilename "). Der POSIX -Standard sagt, dass zwischen ihnen ein Leerzeichen sein muss, das im Fall von "- longoption" das Options-Parsing beenden und alle Long-Optionen in Nicht-Options-Argumente umwandeln würde.

184
Arvid Requate

Der integrierte Befehl getopts ist weiterhin auf AFAIK-Optionen beschränkt.

Es gibt (oder gab es früher) ein externes Programm getopt, das einen Satz von Optionen so reorganisiert, dass es einfacher zu analysieren ist. Sie können dieses Design auch für lange Optionen anpassen. Verwendungsbeispiel:

aflag=no
bflag=no
flist=""
set -- $(getopt abf: "[email protected]")
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 getoptlong-Befehl verwenden.

Beachten Sie, dass die grundlegende Schwäche beim externen getopt-Programm die Schwierigkeit ist, Argumente mit Leerzeichen zu behandeln und diese Leerzeichen korrekt zu erhalten. Aus diesem Grund ist die eingebaute getopts überlegen, auch wenn sie nur durch Optionen mit nur einem Buchstaben gekennzeichnet ist.

147

Hier ein Beispiel, bei dem getopt tatsächlich mit langen Optionen verwendet wird:

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: -- "[email protected]")
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
71
sme

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

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

In diesem Handbuch werden lange Optionen als Argumente für die --Option implementiert, sodass --alpha von getopts als - mit Argument alpha und --bravo=foo als - mit Argument bravo=foo angesehen wird. Das wahre Argument kann mit einem einfachen Ersatz geerntet werden: ${OPTARG#*=}.

In diesem Beispiel hat -b (und seine Langform --bravo) eine obligatorische Option (beachten Sie die manuelle Rekonstruktion der Durchsetzung der Langform). Nicht-boolesche Optionen für lange Argumente kommen nach Gleichheitszeichen, z. --bravo=foo (Leerzeichen für lange Optionen sind schwer zu implementieren).

Da dies getopts verwendet, unterstützt diese Lösung die Verwendung von cmd -ac --bravo=foo -d FILE (die kombinierte Optionen -a und -c enthält und lange Optionen mit Standardoptionen verschachtelt), während die meisten anderen Antworten hier Schwierigkeiten haben oder dies nicht tun.

while getopts ab:c-: arg; do
  case $arg in
    a )  ARG_A=true ;;
    b )  ARG_B="$OPTARG" ;;
    c )  ARG_C=true ;;
    - )  LONG_OPTARG="${OPTARG#*=}"
         case $OPTARG in
           alpha    )  ARG_A=true ;;
           bravo=?* )  ARG_B="$LONG_OPTARG" ;;
           bravo*   )  echo "No arg for --$OPTARG option" >&2; exit 2 ;;
           charlie  )  ARG_C=true ;;
           alpha* | charlie* )
                       echo "No arg allowed for --$OPTARG option" >&2; exit 2 ;;
           '' )        break ;; # "--" terminates argument processing
           * )         echo "Illegal option --$OPTARG" >&2; exit 2 ;;
         esac ;;
    \? ) exit 2 ;;  # getopts already reported the illegal option
  esac
done
shift $((OPTIND-1)) # remove parsed options and args from [email protected] list

Wenn das Argument ein Bindestrich (-) ist, besteht es aus zwei weiteren Komponenten: dem Flag-Namen und (optional) seinem Argument. Ich grenze diese auf die übliche Weise ein, die jeder Befehl mit dem ersten Gleichheitszeichen (=) ausführen würde. $LONG_OPTARG ist daher lediglich der Inhalt von $OPTARG ohne den Flag-Namen oder das Gleichheitszeichen.

Die innere Variable case implementiert lange Optionen manuell, so dass etwas Housekeeping erforderlich ist:

  • bravo=? stimmt mit --bravo=foo überein, aber nicht --bravo= (Hinweis: case stoppt nach dem ersten Treffer)
  • bravo* folgt und notiert das fehlende erforderliche Argument in --bravo und --bravo=
  • alpha* | charlie* fängt Argumente ab, die für die Optionen angegeben wurden, die sie nicht unterstützen
  • '' unterstützt keine Optionen, die mit Bindestrichen beginnen
  • * fängt alle anderen langen Optionen ab und erstellt den von getopts geworfenen Fehler für eine ungültige Option neu

Sie benötigen nicht unbedingt alle diese Haushaltsgegenstände. Beispielsweise möchten Sie, dass --bravo ein optional -Argument hat (das -b aufgrund einer Einschränkung in getopts nicht unterstützen kann). Entfernen Sie lediglich den =? und den zugehörigen Fehlerfall und rufen Sie dann ${ARG_B:=$DEFAULT_ARG_B} auf, wenn Sie $ARG_B zum ersten Mal verwenden.

39
Adam Katz

Schauen Sie sich shFlags an, eine tragbare Shell-Bibliothek (Bedeutung: 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. Außerdem bietet es eine automatisch generierte Verwendungsfunktion.

Hier ist ein einfacher Hello, world! mit 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 "[email protected]" || exit 1
eval set -- "${FLAGS_ARGV}"

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

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

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

Für den Rest 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.

32
user107547

Verwendung von getopts mit kurzen/langen Optionen und Argumenten


Funktioniert mit allen Kombinationen, z.

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

Einige Deklarationen für dieses Beispiel

[email protected]
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 Usage-Funktion 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"

Das Obige zu einem zusammenhängenden Skript zusammenfassen

#!/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 ######
[email protected]
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
30
RapaNui

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
21
mtvee

Ich habe diesen Weg irgendwie gelöst:

# A string with command options
[email protected]

# 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? getopt und getopts sind so verwirrend.

18
user339827

Falls Sie die getopt-Abhängigkeit nicht wünschen, 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') "[email protected]"
      continue
      ;;

  # Done with options
    *)
      break
      ;;
  esac

  # for testing purposes:
  echo "$1"

  shift
done

Natürlich können Sie keine langen Stiloptionen mit einem Strich verwenden. Wenn Sie verkürzte Versionen hinzufügen möchten (z. B. --verbos statt --verbose), müssen Sie diese manuell hinzufügen.

Wenn Sie jedoch getopts Funktionalität mit langen Optionen erhalten möchten, ist dies eine einfache Möglichkeit, dies zu tun.

Ich habe dieses Snippet auch in ein Gist eingefügt. 

13
jakesandlund

Die eingebaute Variable getopts kann das nicht. Es gibt ein Äußeres holen Sie sich(1) Programm, das dies kann, aber Sie erhalten es nur unter Linux von util-linux Paket. Es kommt mit einem Beispielskript getopt-parse.bash.

Es gibt auch einen getopts_long als Shell-Funktion. 

11
Nietzche-jou
#!/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]==> [email protected]"

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
8
3ED

In ksh93 unterstützt getopts 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.

6
Richard Lynch

Noch eine andere Version des Rades erfinden ...

Diese Funktion ist ein (hoffentlich) POSIX-kompatibler einfacher 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 nahezu identisch mit GNU getopt. Die Konvertierung ist also trivial.

Natürlich ist dies immer noch ein ziemlich großes Stück Code, das in einem Skript abgelegt werden kann, aber es ist ungefähr die Hälfte der Zeilen der bekannten getopt_long-Shell-Funktion und kann vorzuziehen sein, wenn Sie die vorhandene GNU getopt ersetzen möchten Verwendet.

Dies ist ein ziemlich neuer Code, also YMMV (und auf jeden Fall lassen Sie mich wissen, ob dies aus irgendeinem Grund nicht POSIX-kompatibel ist - Portabilität war von Anfang an die Absicht, 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 "[email protected]") # 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" -- "[email protected]")
# eval set -- ${opts}
#
# We instead use:
# opts=$(posix_getopt "$shortopts" "$longopts" "[email protected]")
# eval "set -- ${opts}"
posix_getopt () { # args: "$shortopts" "$longopts" "[email protected]"
    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 "[email protected]")"
                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 "[email protected]")"
                        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
}

Verwendungsbeispiel:

# 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)" -- "[email protected]")
opts=$(posix_getopt "$shortopts" "$longopts" "[email protected]")
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
5
phils

Ich schreibe nur ab und zu Shell-Skripte und fällt aus der Praxis heraus, daher wird jedes Feedback geschätzt.

Bei Verwendung der von @Arvid Requate vorgeschlagenen Strategie haben wir einige Benutzerfehler festgestellt. Bei einem Benutzer, der vergessen hat, einen Wert einzugeben, wird der Name der nächsten Option versehentlich 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 zur Überprüfung des Benutzerfehlers für die CLI von http://mwiki.wooledge.org/BashFAQ/035 und der manuellen Analyse übernommen. Ich habe eine Fehlerprüfung in die Behandlung der Argumente "-" und "-" eingefügt. 

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

Mein Ansatz hilft Benutzern, die lieber mit oder ohne Gleichheitszeichen lange eingeben. Das heißt, es sollte dieselbe Antwort auf "--loglevel 9" wie "--loglevel = 9" haben. In der -/space-Methode ist es nicht möglich, sicher zu wissen, ob der Benutzer ein Argument vergisst, daher ist ein Erraten erforderlich. 

  1. wenn der Benutzer das Format lang/gleich (--opt =) hat, löst ein Leerzeichen nach = einen Fehler aus, da kein Argument angegeben wurde. 
  2. wenn der Benutzer lange/Leerzeichen-Argumente (--opt) hat, führt dieses Skript zu einem Fehler, wenn kein Argument folgt (Befehlsende) oder wenn das Argument mit einem Bindestrich beginnt.

Wenn Sie damit beginnen, besteht ein interessanter Unterschied zwischen den Formaten "--opt = value" und "--opt value". Mit dem Gleichheitszeichen wird das Befehlszeilenargument als "opt = value" und die zu verarbeitende Arbeit, bei der die Zeichenfolge analysiert wird, beim "=" getrennt. Im Gegensatz dazu lautet der Name des Arguments bei "--opt value" "opt" und wir haben die Herausforderung, den nächsten Wert in der Befehlszeile zu erhalten. Dort verwendete @Arvid Requate $ {! OPTIND}, den indirekten Verweis. Ich verstehe das immer noch nicht, naja, und die Kommentare in BashFAQ scheinen vor diesem Stil zu warnen ( http://mywiki.wooledge.org/BashFAQ/006 ). Übrigens, ich denke nicht, dass die Kommentare des vorherigen Posters zur Wichtigkeit von OPTIND = $ (($ OPTIND + 1)) richtig sind. Ich meine damit, ich sehe keinen Schaden, wenn ich es weglasse.

In der neuesten Version dieses Skripts bedeutet flag -v den Ausdruck von VERBOSE. 

Speichern Sie es in einer Datei mit dem Namen "cli-5.sh", machen Sie eine ausführbare Datei, und jede davon funktioniert oder schlägt 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 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 -v in Betracht ziehen, da es interne Informationen von OPTIND und OPTARG ausgibt

#/usr/bin/env bash

## Paul Johnson
## 20171016
##

## Combines ideas from
## https://stackoverflow.com/questions/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"
4
pauljohn32

Hier finden Sie einige verschiedene Ansätze für die komplexe Optionsanalyse in bash: http://mywiki.wooledge.org/ComplexOptionParsing

Ich habe das folgende erstellt, und ich denke, es ist ein gutes, weil es minimaler Code ist und sowohl lange als auch kurze Optionen funktionieren. Eine lange Option kann bei diesem Ansatz auch mehrere Argumente enthalten.

#!/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
3
user3573558

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
2
jbar

Vielleicht ist es einfacher, ksh nur für den getopts-Teil zu verwenden, wenn Sie lange Befehlszeilenoptionen benötigen, 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
2
efstathiou_e

Ich wollte etwas ohne externe Abhängigkeiten, mit strenger Bash-Unterstützung (-u), und ich brauchte es, um sogar 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 = "Dateiname.ext")
  • speicherplatzoptionen (--file "Dateiname.ext")
  • concatinated Bools (-hvm)

Fügen Sie einfach das Folgende oben in Ihr Skript 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 wie folgt:

# 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 $*
2
Heath Dutton

Ich arbeite schon lange an diesem Thema ... und habe meine eigene Bibliothek erstellt, die Sie in Ihrem Hauptskript verwenden müssen ... __ Siehe libopt4Shell und cd2mpc für ein Beispiel. Ich hoffe es hilft !

2
liealgebra

Ich habe noch nicht genug Vertreter, um seine Lösung zu kommentieren oder abzustimmen, aber die Antwort von sme hat sehr gut für mich funktioniert. Das einzige Problem, auf das ich gestoßen bin, war, dass die Argumente in einzelne Anführungszeichen verpackt werden (ich habe sie also entfernt).

Ich habe auch einige Beispielverwendungen und HELP-Text hinzugefügt. Ich füge meine etwas erweiterte Version hier hinzu:

#!/bin/bash

# getopt example
# from: https://stackoverflow.com/questions/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: -- "[email protected]")
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
1
kghastie

Die akzeptierte Antwort macht einen sehr schönen Job und weist auf alle Unzulänglichkeiten der bash-eingebauten getopts hin. Die Antwort endet mit:

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

Auch wenn ich dieser Aussage im Prinzip zustimme, ist es meiner Meinung nach die Tatsache, dass wir diese Funktion in verschiedenen Skripten immer wieder einsetzen, um eine "standardisierte", gut getestete Lösung zu schaffen.

Als solches habe ich bash in getopts durch die Implementierung von getopts_long in pure bash ohne externe Abhängigkeiten "aufgerüstet". Die Verwendung der Funktion ist zu 100% kompatibel mit der eingebauten getopts.

Indem Sie getopts_long (das auf GitHub unter https://github.com/UmkaDK/getopts_long gehostet wird) in einem Skript enthalten, kann die Antwort auf die ursprüngliche Frage so einfach wie folgt implementiert werden:

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
1
UmkaDK

Das eingebaute OS X (BSD) getopt unterstützt keine langen Optionen, die Version GNU 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.

0
wprl

Ein einfaches DIY, um nur lange benannte Args zu bekommen:

Benutzen:

$ ./test-args.sh --a1 a1 --a2 "a 2" --a3 --a4= --a5=a5 --a6="a 6"
a1 = "a1"
a2 = "a 2"
a3 = "TRUE"
a4 = ""
a5 = "a5"
a6 = "a 6"
a7 = ""

Skript:

#!/bin/bash

function main() {
    ARGS=`getArgs "[email protected]"`

    a1=`echo "$ARGS" | getNamedArg a1`
    a2=`echo "$ARGS" | getNamedArg a2`
    a3=`echo "$ARGS" | getNamedArg a3`
    a4=`echo "$ARGS" | getNamedArg a4`
    a5=`echo "$ARGS" | getNamedArg a5`
    a6=`echo "$ARGS" | getNamedArg a6`
    a7=`echo "$ARGS" | getNamedArg a7`

    echo "a1 = \"$a1\""
    echo "a2 = \"$a2\""
    echo "a3 = \"$a3\""
    echo "a4 = \"$a4\""
    echo "a5 = \"$a5\""
    echo "a6 = \"$a6\""
    echo "a7 = \"$a7\""

    exit 0
}


function getArgs() {
    for arg in "[email protected]"; do
        echo "$arg"
    done
}


function getNamedArg() {
    ARG_NAME=$1

    sed --regexp-extended --quiet --expression="
        s/^--$ARG_NAME=(.*)\$/\1/p  # Get arguments in format '--arg=value': [s]ubstitute '--arg=value' by 'value', and [p]rint
        /^--$ARG_NAME\$/ {          # Get arguments in format '--arg value' ou '--arg'
            n                       # - [n]ext, because in this format, if value exists, it will be the next argument
            /^--/! p                # - If next doesn't starts with '--', it is the value of the actual argument
            /^--/ {                 # - If next do starts with '--', it is the next argument and the actual argument is a boolean one
                # Then just repla[c]ed by TRUE
                c TRUE
            }
        }
    "
}


main "[email protected]"
0
lmcarreiro

Um plattformübergreifend zu bleiben und das Vertrauen auf externe ausführbare Dateien zu vermeiden, habe ich etwas 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 "[email protected]"

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 )"

Die erforderliche BASH ist etwas länger als sie sein könnte, aber ich wollte vermeiden, auf die assoziativen Arrays von BASH 4 zu setzen. 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: "[email protected]"

    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 "[email protected]"

    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 "[email protected]"
fi
0
Orwellophile

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
0
Renato Silva

Builtin getopts parst nur kurze Optionen (außer in ksh93), Sie können jedoch immer noch ein paar Skriptzeilen hinzufügen, damit getopts lange Optionen behandelt.

Hier ist ein Teil des Codes in 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

Ansonsten in der letzten Korn-Shell ksh93 kann getopts natürlich lange Optionen analysieren und sogar eine Man-Page anzeigen. (Siehe http://www.uxora.com/unix/Shell-script/20-getopts-with-man-page-and-long-options )

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 diese before 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 ;-)

0
commonpike

wenn Sie das Skript einfach so aufrufen möchten

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

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

versuchen Sie dies, hoffe, das ist nützlich

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



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


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
0
Ashish Shetkar