2542 Stimmen

Wie analysiere ich Kommandozeilenargumente in der Bash?

Angenommen, ich habe ein Skript, das mit dieser Zeile aufgerufen wird:

./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile

oder dieses:

./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile 

Wie kann man dies so interpretieren, dass in jedem Fall (oder einer Kombination von beiden) $v , $f y $d werden alle auf true y $outFile wird gleich sein mit /fizz/someOtherFile ?

1 Stimmen

Für zsh-Benutzer gibt es ein großartiges builtin namens zparseopts, das das kann: zparseopts -D -E -M -- d=debug -debug=d Und haben beide -d y --debug im $debug Array echo $+debug[1] gibt 0 oder 1 zurück, wenn einer dieser Werte verwendet wird. Ref: zsh.org/mla/users/2011/msg00350.html

2 Stimmen

Ein wirklich gutes Tutorial: linuxcommand.org/lc3_wss0120.php . Besonders gut gefällt mir das Beispiel "Befehlszeilenoptionen".

0 Stimmen

Ich habe ein Skript erstellt, das dies für Sie erledigt, es heißt - github.com/unfor19/bargs

3551voto

Bruno Bronosky Punkte 60135

Bash Space-Separated (z.B., --option argument )

cat >/tmp/demo-space-separated.sh <<'EOF'
#!/bin/bash

POSITIONAL_ARGS=()

while [[ $# -gt 0 ]]; do
  case $1 in
    -e|--extension)
      EXTENSION="$2"
      shift # past argument
      shift # past value
      ;;
    -s|--searchpath)
      SEARCHPATH="$2"
      shift # past argument
      shift # past value
      ;;
    --default)
      DEFAULT=YES
      shift # past argument
      ;;
    -*|--*)
      echo "Unknown option $1"
      exit 1
      ;;
    *)
      POSITIONAL_ARGS+=("$1") # save positional arg
      shift # past argument
      ;;
  esac
done

set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters

echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)

if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 "$1"
fi
EOF

chmod +x /tmp/demo-space-separated.sh

/tmp/demo-space-separated.sh -e conf -s /etc /etc/hosts
Ausgabe durch Kopieren und Einfügen des obigen Blocks
FILE EXTENSION  = conf
SEARCH PATH     = /etc
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com
Verwendung
demo-space-separated.sh -e conf -s /etc /etc/hosts

Bash Equals-Separated (z.B., --option=argument )

cat >/tmp/demo-equals-separated.sh <<'EOF'
#!/bin/bash

for i in "$@"; do
  case $i in
    -e=*|--extension=*)
      EXTENSION="${i#*=}"
      shift # past argument=value
      ;;
    -s=*|--searchpath=*)
      SEARCHPATH="${i#*=}"
      shift # past argument=value
      ;;
    --default)
      DEFAULT=YES
      shift # past argument with no value
      ;;
    -*|--*)
      echo "Unknown option $i"
      exit 1
      ;;
    *)
      ;;
  esac
done

echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)

if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 $1
fi
EOF

chmod +x /tmp/demo-equals-separated.sh

/tmp/demo-equals-separated.sh -e=conf -s=/etc /etc/hosts
Ausgabe durch Kopieren und Einfügen des obigen Blocks
FILE EXTENSION  = conf
SEARCH PATH     = /etc
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com
Verwendung
demo-equals-separated.sh -e=conf -s=/etc /etc/hosts

Zum besseren Verständnis ${i#*=} Suche nach "Teilstring-Entfernung" in ce guide . Sie ist funktional äquivalent zu `sed 's/[^=]*=//' <<< "$i"` der einen unnötigen Unterprozess aufruft oder `echo "$i" | sed 's/[^=]*=//'` die aufruft deux unnötige Unterprozesse.


Verwendung von bash mit getopt[s]

getopt(1)-Beschränkungen (ältere, relativ junge getopt Versionen):

  • kann nicht mit Argumenten umgehen, die leere Zeichenketten sind
  • kann nicht mit Argumenten mit eingebetteten Leerzeichen umgehen

Neuerdings getopt Versionen haben diese Beschränkungen nicht. Weitere Informationen finden Sie hier docs .


POSIX getopts

Zusätzlich bieten die POSIX-Shell und andere getopts die diese Einschränkungen nicht haben. Ich habe eine vereinfachte getopts Beispiel.

cat >/tmp/demo-getopts.sh <<'EOF'
#!/bin/sh

# A POSIX variable
OPTIND=1         # Reset in case getopts has been used previously in the shell.

# Initialize our own variables:
output_file=""
verbose=0

while getopts "h?vf:" opt; do
  case "$opt" in
    h|\?)
      show_help
      exit 0
      ;;
    v)  verbose=1
      ;;
    f)  output_file=$OPTARG
      ;;
  esac
done

shift $((OPTIND-1))

[ "${1:-}" = "--" ] && shift

echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"
EOF

chmod +x /tmp/demo-getopts.sh

/tmp/demo-getopts.sh -vf /etc/hosts foo bar
Ausgabe durch Kopieren und Einfügen des obigen Blocks
verbose=1, output_file='/etc/hosts', Leftovers: foo bar
Verwendung
demo-getopts.sh -vf /etc/hosts foo bar

Die Vorteile von getopts sind:

  1. Es ist leichter zu transportieren und funktioniert auch in anderen Shells wie dash .
  2. Es kann mehrere Einzeloptionen verarbeiten, wie -vf filename in typischer Unix-Manier, automatisch.

Der Nachteil von getopts ist, dass es nur kurze Optionen verarbeiten kann ( -h , nicht --help ) ohne zusätzlichen Code.

Es gibt eine getopts-Anleitung die die Bedeutung der Syntax und der Variablen erklärt. In der Bash gibt es auch help getopts die aufschlussreich sein könnten.

58 Stimmen

Ist das wirklich wahr? Laut Wikipedia gibt es eine neuere GNU-erweiterte Version von getopt die die gesamte Funktionalität von getopts und einiges mehr. man getopt auf Ubuntu 13.04 Ausgaben getopt - parse command options (enhanced) als Name, so dass ich davon ausgehe, dass diese verbesserte Version jetzt Standard ist.

57 Stimmen

Dass etwas auf Ihrem System auf eine bestimmte Art und Weise funktioniert, ist eine sehr schwache Prämisse für die Annahme, dass es "Standard" ist.

17 Stimmen

@Livven, das getopt ist kein GNU-Dienstprogramm, es ist Teil von util-linux .

786voto

Robert Siemer Punkte 28819

Keine Antwort zeigt Vitrinen erweitertes getopt . Und die bestbewertete Antwort ist irreführend: Sie ignoriert entweder -vfd Stil kurze Optionen (vom OP gefordert) oder Optionen nach Positionsargumenten (ebenfalls vom OP gefordert); und es ignoriert Parsing-Fehler. Stattdessen:

  • Verwendung der erweiterten getopt von util-linux oder früher GNU glibc . 1
  • Es funktioniert mit getopt_long() die C-Funktion der GNU glibc.
  • keine andere Lösung auf dieser Seite kann all dies leisten :
    • behandelt Leerzeichen, Anführungszeichen und sogar Binärzeichen in Argumenten 2 (unverstärkt getopt kann dies nicht tun)
    • es kann mit Optionen am Ende umgehen: script.sh -o outFile file1 file2 -v ( getopts tut dies nicht)
    • ermöglicht = -Stil lange Optionen: script.sh --outfile=fileOut --infile fileIn (beides zuzulassen ist langwierig, wenn es selbst analysiert wird)
    • ermöglicht kombinierte Kurzoptionen, z. B. -vfd (echte Arbeit, wenn selbst analysierend)
    • erlaubt das Berühren von Options-Argumenten, z.B. -oOutfile o -vfdoOutfile
  • Ist schon so alt 3 dass kein GNU-System dies vermisst (z.B. jedes Linux hat es).
  • Sie können das Vorhandensein mit testen: getopt --test Rückgabewert 4.
  • Andere getopt oder Muschelbau getopts sind von begrenztem Nutzen.

Die folgenden Aufrufe

myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile
myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile
myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd
myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile

alle Rückkehr

verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile

mit den folgenden myscript

#!/bin/bash
# More safety, by turning some bugs into errors.
# Without `errexit` you don’t need ! and can replace
# ${PIPESTATUS[0]} with a simple $?, but I prefer safety.
set -o errexit -o pipefail -o noclobber -o nounset

# -allow a command to fail with !’s side effect on errexit
# -use return value from ${PIPESTATUS[0]}, because ! hosed $?
! getopt --test > /dev/null 
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
    echo 'I’m sorry, `getopt --test` failed in this environment.'
    exit 1
fi

# option --output/-o requires 1 argument
LONGOPTS=debug,force,output:,verbose
OPTIONS=dfo:v

# -regarding ! and PIPESTATUS see above
# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via   -- "$@"   to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
    # e.g. return value is 1
    #  then getopt has complained about wrong arguments to stdout
    exit 2
fi
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED"

d=n f=n v=n outFile=-
# now enjoy the options in order and nicely split until we see --
while true; do
    case "$1" in
        -d|--debug)
            d=y
            shift
            ;;
        -f|--force)
            f=y
            shift
            ;;
        -v|--verbose)
            v=y
            shift
            ;;
        -o|--output)
            outFile="$2"
            shift 2
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "Programming error"
            exit 3
            ;;
    esac
done

# handle non-option arguments
if [[ $# -ne 1 ]]; then
    echo "$0: A single input file is required."
    exit 4
fi

echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"

1 enhanced getopt ist auf den meisten "bash-systems" verfügbar, einschließlich Cygwin; unter OS X versuchen Sie brew install gnu-getopt o sudo port install getopt
2 die POSIX exec() Konventionen haben keine zuverlässige Möglichkeit, binäre NULL in Befehlszeilenargumenten zu übergeben; diese Bytes beenden das Argument vorzeitig
3 die erste Version wurde 1997 oder früher veröffentlicht (ich habe sie nur bis 1997 zurückverfolgt)

8 Stimmen

Vielen Dank dafür. Gerade bestätigt von der Feature-Tabelle unter de.wikipedia.org/wiki/Getopts wenn Sie Unterstützung für lange Optionen benötigen und nicht mit Solaris arbeiten, getopt ist der richtige Weg.

8 Stimmen

Ich glaube, dass der einzige Vorbehalt bei getopt ist, dass es nicht verwendet werden kann Bequem in Wrapper-Skripten, bei denen man einige wenige Optionen speziell für das Wrapper-Skript hat, und dann die Optionen, die nicht für das Wrapper-Skript gelten, intakt an die verpackte ausführbare Datei weitergeben kann. Sagen wir, ich habe ein grep Wrapper genannt mygrep und ich habe eine Option --foo spezifisch für mygrep dann kann ich nicht tun mygrep --foo -A 2 und haben die -A 2 automatisch übergeben an grep ; I brauchen zu tun mygrep --foo -- -A 2 . Hier ist meine Umsetzung zusätzlich zu Ihrer Lösung.

0 Stimmen

Diese Antwort gefällt mir. Ich finde die Aussage von "wooledge.org", dass die Verwendung von enhanced getopt bedeutet, dass man doppelt so viel Arbeit hat, eher irreführend. Die meisten der Skripte schreibe ich für mich. Sie müssen auf meinem System laufen. Ich gebe sie nie an andere weiter (außer vielleicht an Kollegen, wenn es sich um diese Art von Skript handelt). Ich muss höchstens testen, ob Enhanced Getopt installiert ist, und mich dann mit der Meldung "Enhanced Getopt ist erforderlich, um dieses Skript auszuführen" zurückziehen. Ein Kinderspiel. Abgesehen davon bin ich mir nicht sicher, ob ich jemals gesehen habe, dass enhanced getopt standardmäßig installiert ist. Es ist Teil von linux-util, das bei den meisten Distributionen ein Entwicklungspaket ist.

396voto

Inanc Gumus Punkte 20769

deploy.sh

#!/bin/bash

while [[ "$#" -gt 0 ]]; do
    case $1 in
        -t|--target) target="$2"; shift ;;
        -u|--uglify) uglify=1 ;;
        *) echo "Unknown parameter passed: $1"; exit 1 ;;
    esac
    shift
done

echo "Where to deploy: $target"
echo "Should uglify  : $uglify"

Verwendung:

./deploy.sh -t dev -u

# OR:

./deploy.sh --target dev --uglify

4 Stimmen

Das ist es, was ich tue. Müssen while [[ "$#" > 1 ]] wenn ich das Beenden der Zeile mit einem booleschen Flag unterstützen möchte ./script.sh --debug dev --uglify fast --verbose . Beispiel: gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58

30 Stimmen

Wahnsinn! Einfach und sauber! So verwende ich das: gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58

5 Stimmen

Es ist viel einfacher, dies in jedes Skript einzufügen, als sich mit dem Quelltext zu befassen oder die Leute fragen zu lassen, wo Ihre Funktionalität eigentlich beginnt.

162voto

guneysus Punkte 5747

De digitalpeer.de mit geringfügigen Änderungen:

Verwendung myscript.sh -p=my_prefix -s=dirname -l=libname

#!/bin/bash
for i in "$@"
do
case $i in
    -p=*|--prefix=*)
    PREFIX="${i#*=}"

    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    ;;
    -l=*|--lib=*)
    DIR="${i#*=}"
    ;;
    --default)
    DEFAULT=YES
    ;;
    *)
            # unknown option
    ;;
esac
done
echo PREFIX = ${PREFIX}
echo SEARCH PATH = ${SEARCHPATH}
echo DIRS = ${DIR}
echo DEFAULT = ${DEFAULT}

Zum besseren Verständnis ${i#*=} Suche nach "Teilstring-Entfernung" in ce guide . Sie ist funktional äquivalent zu `sed 's/[^=]*=//' <<< "$i"` der einen unnötigen Unterprozess aufruft oder `echo "$i" | sed 's/[^=]*=//'` die aufruft deux unnötige Unterprozesse.

4 Stimmen

Toll! Allerdings funktioniert das nicht bei durch Leerzeichen getrennten Argumenten à la mount -t tempfs ... . Wahrscheinlich kann man dies durch etwas wie while [ $# -ge 1 ]; do param=$1; shift; case $param in; -p) prefix=$1; shift;; usw.

4 Stimmen

Das kann nicht funktionieren -vfd Stil kombinierte kurze Optionen.

1 Stimmen

Wenn Sie generisch auswerten wollen --option y -option ohne Wiederholung OPTION=$i jedes Mal, verwenden Sie -*=*) als Übereinstimmungsmuster und eval ${i##*-} .

119voto

bronson Punkte 4626
while [ "$#" -gt 0 ]; do
  case "$1" in
    -n) name="$2"; shift 2;;
    -p) pidfile="$2"; shift 2;;
    -l) logfile="$2"; shift 2;;

    --name=*) name="${1#*=}"; shift 1;;
    --pidfile=*) pidfile="${1#*=}"; shift 1;;
    --logfile=*) logfile="${1#*=}"; shift 1;;
    --name|--pidfile|--logfile) echo "$1 requires an argument" >&2; exit 1;;

    -*) echo "unknown option: $1" >&2; exit 1;;
    *) handle_argument "$1"; shift 1;;
  esac
done

Diese Lösung:

  • Griffe -n arg y --name=arg
  • erlaubt Argumente am Ende
  • zeigt normale Fehler an, wenn etwas falsch geschrieben ist
  • kompatibel, verwendet keine Bashismen
  • lesbar, erfordert nicht die Aufrechterhaltung des Zustands in einer Schleife

4 Stimmen

Entschuldigung für die Verzögerung. In meinem Skript empfängt die Funktion handle_argument alle Nicht-Options-Argumente. Sie können diese Zeile durch etwas anderes ersetzen, z. B. *) die "unrecognized argument: $1" oder die Args in einer Variablen sammeln *) args+="$1"; shift 1;; .

0 Stimmen

Erstaunlich! Ich habe einige Antworten getestet, aber dies ist die einzige, die in allen Fällen funktioniert hat, einschließlich vieler Positionsparameter (sowohl vor als auch nach Flaggen)

3 Stimmen

Netter, knapper Code, aber die Verwendung von -n und keinem anderen Argument führt zu einer Endlosschleife aufgrund eines Fehlers bei shift 2 ausgestellt shift zweimal anstelle von shift 2 . Schlug die Bearbeitung vor.

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