Frage Wie parse ich Befehlszeilenargumente in Bash?


Sag, 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 

Was ist der akzeptierte Weg, dies so zu analysieren, dass in jedem Fall (oder eine Kombination der beiden) $v, $f, und $d werden alle eingestellt auf true und $outFile wird gleich sein /fizz/someOtherFile ?


1340
2017-10-10 16:57


Ursprung


Antworten:


Methode # 1: Bash ohne getopt [s] verwenden

Zwei gängige Methoden zum Übergeben von Schlüssel / Wert-Paar-Argumenten sind:

Bash Space-Separated (z. B. --option argument) (ohne getopt [s])

Verwendung ./myscript.sh -e conf -s /etc -l /usr/lib /etc/hosts

#!/bin/bash

POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"

case $key in
    -e|--extension)
    EXTENSION="$2"
    shift # past argument
    shift # past value
    ;;
    -s|--searchpath)
    SEARCHPATH="$2"
    shift # past argument
    shift # past value
    ;;
    -l|--lib)
    LIBPATH="$2"
    shift # past argument
    shift # past value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument
    ;;
    *)    # unknown option
    POSITIONAL+=("$1") # save it in an array for later
    shift # past argument
    ;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters

echo FILE EXTENSION  = "${EXTENSION}"
echo SEARCH PATH     = "${SEARCHPATH}"
echo LIBRARY PATH    = "${LIBPATH}"
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

Bash ist gleich (z. B. --option=argument) (ohne getopt [s])

Verwendung ./myscript.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

#!/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
    ;;
    -l=*|--lib=*)
    LIBPATH="${i#*=}"
    shift # past argument=value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument with no value
    ;;
    *)
          # unknown option
    ;;
esac
done
echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
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

Um besser zu verstehen ${i#*=} Suche nach "Substring Entfernung" in dieser Leitfaden. Es ist funktionell äquivalent zu `sed 's/[^=]*=//' <<< "$i"` was einen unnötigen Subprozess oder ruft `echo "$i" | sed 's/[^=]*=//'` welches anruft zwei unnötige Teilprozesse.

Getopt [s] benutzen

von: http://mywiki.wooledge.org/BashFAQ/035#getopts

getopt (1) Einschränkungen (älter, relativ neu getopt Versionen):

  • kann keine Argumente verarbeiten, die leere Zeichenfolgen sind
  • kann keine Argumente mit eingebetteten Leerzeichen verarbeiten

Neuere getopt Versionen haben diese Einschränkungen nicht.

Zusätzlich bieten die POSIX-Shell (und andere) getopts das hat diese Einschränkungen nicht. Hier ist ein simplistic getopts Beispiel:

#!/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: $@"

# End of file

Die Vorteile von getopts sind:

  1. Es ist tragbarer und funktioniert in anderen Shells wie dash.
  2. Es kann mehrere einzelne Optionen wie -vf filename in der typischen Unix-Art, automatisch.

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

Da ist ein Getopts Tutorial was erklärt, was die ganze Syntax und die Variablen bedeuten. In bash gibt es auch help getopts, die informativ sein könnten.


1936
2018-01-07 20:01



Keine Antwort erwähnt verbessertes getopt. Und das Top-Wahl Antwort ist irreführend: Es ignoriert -⁠vfd Style Short-Optionen (vom OP angefordert), Optionen nach Positionsargumenten (auch vom OP angefordert) und Parsing-Fehler werden ignoriert. Stattdessen:

  • Verwenden Sie erweitert getopt von util-linux oder früher GNU glibc.1
  • Es funktioniert mit getopt_long() die C-Funktion von GNU glibc.
  • Hat alle nützliche Unterscheidungsmerkmale (die anderen haben sie nicht):
    • behandelt Leerzeichen, zitiert Zeichen und sogar binär in Argumenten2
    • Es kann Optionen am Ende behandeln: script.sh -o outFile file1 file2 -v
    • erlaubt =-Stil lange Optionen: script.sh --outfile=fileOut --infile fileIn
  • Ist schon so alt3 dass kein GNU-System dies vermisst (z. B. Linux hat es).
  • Sie können auf seine Existenz testen mit: getopt --test → Rückgabewert 4.
  • Andere getopt oder Shell-Built-in getopts sind von begrenztem Nutzen.

Die folgenden Anrufe

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 zurück

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

mit den folgenden myscript

#!/bin/bash
# saner programming env: these switches turn some bugs into errors
set -o errexit -o pipefail -o noclobber -o nounset

! getopt --test > /dev/null 
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
    echo "I’m sorry, `getopt --test` failed in this environment."
    exit 1
fi

OPTIONS=dfo:v
LONGOPTS=debug,force,output:,verbose

# -use ! and PIPESTATUS to get exit code with errexit set
# -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 erweitertes getopt ist auf den meisten "Bash-Systemen" verfügbar, einschließlich Cygwin; auf OS X versuchen Brauen installieren Gnu-Getopt
2 das POSIX exec() Konventionen haben keine zuverlässige Möglichkeit, binäre NULL in Befehlszeilenargumenten zu übergeben. Diese Bytes beenden das Argument vorzeitig
3 erste Version, die 1997 oder früher veröffentlicht wurde (ich habe sie nur bis 1997 zurückverfolgt)


328
2018-04-20 17:47



von : digitalpeer.com mit kleinen Ä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}

Um besser zu verstehen ${i#*=} Suche nach "Substring Entfernung" in dieser Leitfaden. Es ist funktionell äquivalent zu `sed 's/[^=]*=//' <<< "$i"` was einen unnötigen Subprozess oder ruft `echo "$i" | sed 's/[^=]*=//'` welches anruft zwei unnötige Teilprozesse.


103
2017-11-13 10:31



getopt()/getopts() ist eine gute Option. Gestohlen von Hier:

Die einfache Verwendung von "getopt" wird in diesem Mini-Skript gezeigt:

#!/bin/bash
echo "Before getopt"
for i
do
  echo $i
done
args=`getopt abc:d $*`
set -- $args
echo "After getopt"
for i
do
  echo "-->$i"
done

Was wir gesagt haben ist, dass jeder von -a,   -b, -c oder -d wird erlaubt, aber auf -c folgt ein Argument (das "c:" sagt das).

Wenn wir dieses "g" nennen und es ausprobieren:

bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--

Wir beginnen mit zwei Argumenten und   "getopt" bricht die Optionen auf und   setzt jedes in sein eigenes Argument. Es auch   hinzugefügt "--".


101
2017-10-10 17:03



Auf die Gefahr hin, ein weiteres Beispiel hinzuzufügen, um es zu ignorieren, hier ist mein Schema.

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

Ich hoffe, es ist nützlich für jemanden.

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

59
2017-07-15 23:43



Kurzer Weg

script.sh

#!/bin/bash

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

echo "Should deploy? $deploy"
echo "Should uglify? $uglify"

Verwendung:

./script.sh -d dev -u

# OR:

./script.sh --deploy dev --uglify

39
2017-11-20 12:28



Ich bin ca. 4 Jahre zu spät zu dieser Frage, möchte aber zurückgeben. Ich benutzte die früheren Antworten als Ausgangspunkt, um mein altes Adhoc-Param-Parsing aufzuräumen. Ich habe dann den folgenden Template-Code überarbeitet. Es behandelt sowohl lange als auch kurze Parameter, wobei = oder Leerzeichen getrennte Argumente sowie mehrere kurze Parameter verwendet werden. Schließlich fügt es alle Nicht-Parameter-Argumente wieder in die Variablen $ 1, $ 2 .. ein. Ich hoffe es ist nützlich.

#!/usr/bin/env bash

# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z "$BASH_VERSION" ]; then bash $0 $@ ; exit $? ; fi

echo "Before"
for i ; do echo - $i ; done


# Code template for parsing command line parameters using only portable shell
# code, while handling both long and short params, handling '-f file' and
# '-f=file' style param data and also capturing non-parameters to be inserted
# back into the shell positional parameters.

while [ -n "$1" ]; do
        # Copy so we can modify it (can't modify $1)
        OPT="$1"
        # Detect argument termination
        if [ x"$OPT" = x"--" ]; then
                shift
                for OPT ; do
                        REMAINS="$REMAINS \"$OPT\""
                done
                break
        fi
        # Parse current opt
        while [ x"$OPT" != x"-" ] ; do
                case "$OPT" in
                        # Handle --flag=value opts like this
                        -c=* | --config=* )
                                CONFIGFILE="${OPT#*=}"
                                shift
                                ;;
                        # and --flag value opts like this
                        -c* | --config )
                                CONFIGFILE="$2"
                                shift
                                ;;
                        -f* | --force )
                                FORCE=true
                                ;;
                        -r* | --retry )
                                RETRY=true
                                ;;
                        # Anything unknown is recorded for later
                        * )
                                REMAINS="$REMAINS \"$OPT\""
                                break
                                ;;
                esac
                # Check for multiple short options
                # NOTICE: be sure to update this pattern to match valid options
                NEXTOPT="${OPT#-[cfr]}" # try removing single short opt
                if [ x"$OPT" != x"$NEXTOPT" ] ; then
                        OPT="-$NEXTOPT"  # multiple short opts, keep going
                else
                        break  # long form, exit inner loop
                fi
        done
        # Done with that param. move to next
        shift
done
# Set the non-parameters back into the positional parameters ($1 $2 ..)
eval set -- $REMAINS


echo -e "After: \n configfile='$CONFIGFILE' \n force='$FORCE' \n retry='$RETRY' \n remains='$REMAINS'"
for i ; do echo - $i ; done

36
2017-07-01 01:20



Meine Antwort basiert weitgehend auf die Antwort von Bruno Bronosky, aber ich habe seine beiden reinen Bash-Implementierungen in eine Art zusammengefasst, die ich ziemlich häufig benutze.

# As long as there is at least one more argument, keep looping
while [[ $# -gt 0 ]]; do
    key="$1"
    case "$key" in
        # This is a flag type option. Will catch either -f or --foo
        -f|--foo)
        FOO=1
        ;;
        # Also a flag type option. Will catch either -b or --bar
        -b|--bar)
        BAR=1
        ;;
        # This is an arg value type option. Will catch -o value or --output-file value
        -o|--output-file)
        shift # past the key and to the value
        OUTPUTFILE="$1"
        ;;
        # This is an arg=value type option. Will catch -o=value or --output-file=value
        -o=*|--output-file=*)
        # No need to shift here since the value is part of the same string
        OUTPUTFILE="${key#*=}"
        ;;
        *)
        # Do whatever you want with extra options
        echo "Unknown option '$key'"
        ;;
    esac
    # Shift after checking all the cases to get the next option
    shift
done

Auf diese Weise können Sie sowohl durch Leerzeichen getrennte Optionen / Werte als auch gleich definierte Werte erhalten.

So könnten Sie Ihr Skript ausführen mit:

./myscript --foo -b -o /fizz/file.txt

ebenso gut wie:

./myscript -f --bar -o=/fizz/file.txt

und beide sollten das gleiche Endergebnis haben.

Vorteile:

  • Erlaubt beide -arg = Wert und -arg Wert

  • Funktioniert mit jedem Arg-Namen, den Sie in bash verwenden können

    • Bedeutung -a oder -arg oder -arg oder -a-r-g oder was auch immer
  • Reine Bash. Keine Notwendigkeit, getopt oder getopts zu lernen / verwenden

CONS:

  • Kann Argumente nicht kombinieren

    • Bedeutung nein -abc. Sie müssen -a -b -c tun

Dies sind die einzigen Vorteile / Nachteile, die ich mir von Kopf bis Fuß vorstellen kann


21
2017-09-08 18:59



Ich habe das Problem gefunden, portables Parsen in Skripten zu schreiben, so frustrierend, dass ich geschrieben habe Argbasch - ein FOSS-Code-Generator, der den Parsing-Code für Ihr Skript generieren kann und einige nette Features hat:

https://argbash.io


21
2017-07-10 22:40