it-swarm.com.de

Wie analysiere ich Befehlszeilenargumente in Bash?

Sagen wir, 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 einer Kombination der beiden) $v, $f und $d alle auf true gesetzt werden und $outFile gleich /fizz/someOtherFile ist?

1521

Methode 1: bash ohne getopt [s] verwenden

Zwei übliche Methoden zum Übergeben von Schlüsselwertpaaren sind:

Leerzeichen getrennt durch Bash (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 Equals-Separated (z. B. --option=argument) (ohne getopt [s])

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

#!/bin/bash

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

Zum besseren Verständnis ${i#*=} suchen Sie nach "Substring-Entfernung" in diesem Leitfaden . Es ist funktional äquivalent zu `sed 's/[^=]*=//' <<< "$i"`, das einen unnötigen Unterprozess aufruft, oder `echo "$i" | sed 's/[^=]*=//'`, der zwei unnötige Unterprozesse aufruft. 

Methode 2: bash mit getopt [s] verwenden

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

getopt (1) Einschränkungen (ältere, relativ neue getopt-Versionen): 

  • argumente, die leere Zeichenfolgen sind, können nicht verarbeitet werden
  • argumente mit eingebettetem Leerzeichen können nicht verarbeitet werden

Neuere Versionen von getopt haben diese Einschränkungen nicht.

Darüber hinaus bietet die POSIX-Shell (und andere) getopts, für die diese Einschränkungen nicht gelten. Hier ist ein einfaches 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: [email protected]"

# End of file

Die Vorteile von getopts sind:

  1. Es ist portabler und funktioniert auch in anderen Shells wie dash
  2. Es kann mehrere Einzeloptionen wie -vf filename auf die typische Unix-Art automatisch verarbeiten.

Der Nachteil von getopts ist, dass nur kurze Optionen (-h, nicht --help) ohne zusätzlichen Code verarbeitet werden können.

Es gibt ein getopts Tutorial , das erklärt, was alle Syntax und Variablen bedeuten. In der Bash gibt es auch help getopts, was informativ sein kann.

2173
Bruno Bronosky

Keine Antwort erwähnt verbesserte Getopt. Und die Top-gestimmte Antwort ist irreführend: Sie ignoriert -⁠vfd-Stiloptionen (vom OP angefordert), Optionen nach Positionsargumenten (auch vom OP angefordert) und Parsingfehler. Stattdessen:

  • Verwenden Sie die erweiterte getopt von util-linux oder früher GNU glibc.1
  • Es funktioniert mit getopt_long() der C-Funktion von GNU glibc.
  • Hat alle nützliche Unterscheidungsmerkmale (die anderen haben sie nicht):
    • behandelt Leerzeichen, Anführungszeichen und sogar binäre Argumente2
    • es kann Optionen am Ende behandeln: script.sh -o outFile file1 file2 -v
    • erlaubt =- lange Optionen: script.sh --outfile=fileOut --infile fileIn
  • Ist schon so alt3 dass kein GNU System dies vermisst (z. B. hat es irgendein Linux).
  • Sie können auf Folgendes prüfen: getopt --test → Rückgabewert 4.
  • Andere getopt oder Shell-builtin 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 kehren zurück

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

mit der 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   -- "[email protected]"   to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "[email protected]")
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 verbesserte getopt ist auf den meisten bash-systemen verfügbar, einschließlich Cygwin. unter OS X try brew install gnu-getopt oder Sudo port install getopt
2 Die POSIX exec()-Konventionen haben keine zuverlässige Möglichkeit, binäre NULL-Werte 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 verfolgt)

419
Robert Siemer

von: digitalpeer.com mit geringfügigen Änderungen

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

#!/bin/bash
for i in "[email protected]"
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#*=} suchen Sie nach "Substring-Entfernung" in diesem Leitfaden . Es ist funktional äquivalent zu `sed 's/[^=]*=//' <<< "$i"`, das einen unnötigen Unterprozess aufruft, oder `echo "$i" | sed 's/[^=]*=//'`, der two unnötige Unterprozesse aufruft.

114
guneysus

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 eines von -a, -b, -c oder -d ist erlaubt, aber auf -c folgt ein Argument (das "c:" sagt das).

Wenn wir das "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" zerlegt die Optionen und bringt jede in ihre eigenen Argumente. Es auch hinzugefügt "--".

102
Matt J

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

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

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

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
84
bronson

Kurzer Weg

script.sh

#!/bin/bash

while [[ "$#" -gt 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"

Verwendungszweck:

./script.sh -d dev -u

# OR:

./script.sh --deploy dev --uglify
78
Inanc Gumus

Ich bin etwa 4 Jahre zu spät dran, möchte aber zurückgeben. Ich habe die früheren Antworten als Ausgangspunkt genommen, um mein altes Adhoc-Param-Parsing aufzuräumen. Ich habe dann den folgenden Vorlagencode überarbeitet. Es verarbeitet sowohl lange als auch kurze Parameter, wobei die Argumente = oder durch Leerzeichen getrennt verwendet werden, sowie mehrere kurze Parameter, die zusammen gruppiert sind. Schließlich fügt er 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 [email protected] ; 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
39
Shane Day

Meine Antwort basiert weitgehend auf der Antwort von Bruno Bronosky , aber ich mischte irgendwie seine beiden reinen Bash-Implementierungen zu einer, die ich ziemlich häufig verwende.

# 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

Dadurch können Sie sowohl durch Leerzeichen getrennte Optionen/Werte als auch gleich definierte Werte festlegen.

So können 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.

PROS:

  • Ermöglicht sowohl -arg = Wert als auch -arg Wert

  • Funktioniert mit jedem Argumentnamen, den Sie in Bash verwenden können

    • Bedeutung -a oder -arg oder -arg oder -a-r-g oder was auch immer
  • Pure bash. Getopt oder getopts müssen nicht gelernt werden

CONS:

  • Args können nicht kombiniert werden

    • Bedeutet no -abc. Sie müssen -a -b -c ausführen

Dies sind die einzigen Vor-und Nachteile, die mir aus dem Kopf kommen

25
Ponyboy47

Ich habe die Sache gefunden, tragbares Parsing in Skripten zu schreiben, die so frustrierend sind, dass ich Argbash - einen FOSS-Code-Generator geschrieben hat, der den Argument-Parsing-Code für Ihr Skript generieren kann. Außerdem enthält es einige nette Funktionen:

https://argbash.io

24
bubla

Als Erweiterung der hervorragenden Antwort von @guneysus folgt ein Tweak, mit dem der Benutzer die von ihm bevorzugte Syntax verwenden kann, z

command -x=myfilename.ext --another_switch 

vs

command -x myfilename.ext --another_switch

Das heißt, die Gleichen können durch Leerzeichen ersetzt werden. 

Diese "unscharfe Interpretation" mag Ihnen nicht gefallen, aber wenn Sie Skripte erstellen, die mit anderen Dienstprogrammen austauschbar sind (wie bei mir, was mit ffmpeg funktionieren muss), ist die Flexibilität nützlich.

STD_IN=0

prefix=""
key=""
value=""
for keyValue in "[email protected]"
do
  case "${prefix}${keyValue}" in
    -i=*|--input_filename=*)  key="-i";     value="${keyValue#*=}";; 
    -ss=*|--seek_from=*)      key="-ss";    value="${keyValue#*=}";;
    -t=*|--play_seconds=*)    key="-t";     value="${keyValue#*=}";;
    -|--stdin)                key="-";      value=1;;
    *)                                      value=$keyValue;;
  esac
  case $key in
    -i) MOVIE=$(resolveMovie "${value}");  prefix=""; key="";;
    -ss) SEEK_FROM="${value}";          prefix=""; key="";;
    -t)  PLAY_SECONDS="${value}";           prefix=""; key="";;
    -)   STD_IN=${value};                   prefix=""; key="";; 
    *)   prefix="${keyValue}=";;
  esac
done
13
unsynchronized

Ich denke, das ist einfach zu bedienen:

#!/bin/bash
#

readopt='getopts $opts opt;rc=$?;[ $rc$opt == 0? ]&&exit 1;[ $rc == 0 ]||{ shift $[OPTIND-1];false; }'

opts=vfdo:

# Enumerating options
while eval $readopt
do
    echo OPT:$opt ${OPTARG+OPTARG:$OPTARG}
done

# Enumerating arguments
for arg
do
    echo ARG:$arg
done

Aufrufbeispiel:

./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
OPT:v 
OPT:d 
OPT:o OPTARG:/fizz/someOtherFile
OPT:f 
ARG:./foo/bar/someFile
13
Alek

Ich gebe Ihnen die Funktion parse_params, die Parameter von der Kommandozeile aus analysiert.

  1. Es ist eine reine Bash-Lösung, keine zusätzlichen Dienstprogramme.
  2. Verschmutzt den globalen Geltungsbereich nicht. 
  3. Mühelos erhalten Sie einfach zu verwendende Variablen, auf die Sie weitere Logik aufbauen könnten.
  4. Die Anzahl der Striche vor den Parametern spielt keine Rolle (--all entspricht -all gleich all=all)

Das folgende Skript ist eine Demonstration zum Kopieren und Einfügen. Weitere Informationen zur Verwendung von show_use finden Sie unter parse_params-Funktion.

Einschränkungen: 

  1. Unterstützt keine durch Leerzeichen getrennten Parameter (-d 1)
  2. Param-Namen verlieren Bindestriche, so dass --any-param und -anyparam gleichwertig sind
  3. eval $(parse_params "[email protected]") muss innerhalb von bash function verwendet werden (funktioniert nicht im globalen Gültigkeitsbereich)

#!/bin/bash

# Universal Bash parameter parsing
# Parses equal sign separated params into local variables (--name=bob creates variable $name=="bob")
# Standalone named parameter value will equal its param name (--force creates variable $force=="force")
# Parses multi-valued named params into an array (--path=path1 --path=path2 creates ${path[*]} array)
# Parses un-named params into ${ARGV[*]} array
# Additionally puts all named params raw into ${ARGN[*]} array
# Additionally puts all standalone "option" params raw into ${ARGO[*]} array
# @author Oleksii Chekulaiev
# @version v1.4 (Jun-26-2018)
parse_params ()
{
    local existing_named
    local ARGV=() # un-named params
    local ARGN=() # named params
    local ARGO=() # options (--params)
    echo "local ARGV=(); local ARGN=(); local ARGO=();"
    while [[ "$1" != "" ]]; do
        # Escape asterisk to prevent bash asterisk expansion
        _escaped=${1/\*/\'\"*\"\'}
        # If equals delimited named parameter
        if [[ "$1" =~ ^..*=..* ]]; then
            # Add to named parameters array
            echo "ARGN+=('$_escaped');"
            # key is part before first =
            local _key=$(echo "$1" | cut -d = -f 1)
            # val is everything after key and = (protect from param==value error)
            local _val="${1/$_key=}"
            # remove dashes from key name
            _key=${_key//\-}
            # skip when key is empty
            if [[ "$_key" == "" ]]; then
                shift
                continue
            fi
            # search for existing parameter name
            if (echo "$existing_named" | grep "\b$_key\b" >/dev/null); then
                # if name already exists then it's a multi-value named parameter
                # re-declare it as an array if needed
                if ! (declare -p _key 2> /dev/null | grep -q 'declare \-a'); then
                    echo "$_key=(\"\$$_key\");"
                fi
                # append new value
                echo "$_key+=('$_val');"
            else
                # single-value named parameter
                echo "local $_key=\"$_val\";"
                existing_named=" $_key"
            fi
        # If standalone named parameter
        Elif [[ "$1" =~ ^\-. ]]; then
            # remove dashes
            local _key=${1//\-}
            # skip when key is empty
            if [[ "$_key" == "" ]]; then
                shift
                continue
            fi
            # Add to options array
            echo "ARGO+=('$_escaped');"
            echo "local $_key=\"$_key\";"
        # non-named parameter
        else
            # Escape asterisk to prevent bash asterisk expansion
            _escaped=${1/\*/\'\"*\"\'}
            echo "ARGV+=('$_escaped');"
        fi
        shift
    done
}

#--------------------------- DEMO OF THE USAGE -------------------------------

show_use ()
{
    eval $(parse_params "[email protected]")
    # --
    echo "${ARGV[0]}" # print first unnamed param
    echo "${ARGV[1]}" # print second unnamed param
    echo "${ARGN[0]}" # print first named param
    echo "${ARG0[0]}" # print first option param (--force)
    echo "$anyparam"  # print --anyparam value
    echo "$k"         # print k=5 value
    echo "${multivalue[0]}" # print first value of multi-value
    echo "${multivalue[1]}" # print second value of multi-value
    [[ "$force" == "force" ]] && echo "\$force is set so let the force be with you"
}

show_use "param 1" --anyparam="my value" param2 k=5 --force --multi-value=test1 --multi-value=test2
8

getopts funktioniert bestens, wenn Sie mit # 1 installiert haben und mit # 2 auf derselben Plattform arbeiten möchten. OSX und Linux (z. B.) verhalten sich diesbezüglich unterschiedlich.

Hier ist eine (nicht getopts) -Lösung, die gleich, ungleich und boolesche Flags unterstützt. Beispielsweise können Sie Ihr Skript auf folgende Weise ausführen:

./script --arg1=value1 --arg2 value2 --shouldClean

# parse the arguments.
COUNTER=0
ARGS=("[email protected]")
while [ $COUNTER -lt $# ]
do
    arg=${ARGS[$COUNTER]}
    let COUNTER=COUNTER+1
    nextArg=${ARGS[$COUNTER]}

    if [[ $skipNext -eq 1 ]]; then
        echo "Skipping"
        skipNext=0
        continue
    fi

    argKey=""
    argVal=""
    if [[ "$arg" =~ ^\- ]]; then
        # if the format is: -key=value
        if [[ "$arg" =~ \= ]]; then
            argVal=$(echo "$arg" | cut -d'=' -f2)
            argKey=$(echo "$arg" | cut -d'=' -f1)
            skipNext=0

        # if the format is: -key value
        Elif [[ ! "$nextArg" =~ ^\- ]]; then
            argKey="$arg"
            argVal="$nextArg"
            skipNext=1

        # if the format is: -key (a boolean flag)
        Elif [[ "$nextArg" =~ ^\- ]] || [[ -z "$nextArg" ]]; then
            argKey="$arg"
            argVal=""
            skipNext=0
        fi
    # if the format has not flag, just a value.
    else
        argKey=""
        argVal="$arg"
        skipNext=0
    fi

    case "$argKey" in 
        --source-scmurl)
            SOURCE_URL="$argVal"
        ;;
        --dest-scmurl)
            DEST_URL="$argVal"
        ;;
        --version-num)
            VERSION_NUM="$argVal"
        ;;
        -c|--clean)
            CLEAN_BEFORE_START="1"
        ;;
        -h|--help|-help|--h)
            showUsage
            exit
        ;;
    esac
done
8
vangorra

EasyOptions erfordert keine Analyse:

## Options:
##   --verbose, -v  Verbose mode
##   --output=FILE  Output filename

source easyoptions || exit

if test -n "${verbose}"; then
    echo "output file is ${output}"
    echo "${arguments[@]}"
fi
7
Renato Silva

So mache ich eine Funktion, um zu verhindern, dass Getopts gleichzeitig irgendwo höher im Stack ausgeführt werden:

function waitForWeb () {
   local OPTIND=1 OPTARG OPTION
   local Host=localhost port=8080 proto=http
   while getopts "h:p:r:" OPTION; do
      case "$OPTION" in
      h)
         Host="$OPTARG"
         ;;
      p)
         port="$OPTARG"
         ;;
      r)
         proto="$OPTARG"
         ;;
      esac
   done
...
}
6
akostadinov

Ich möchte meine Version der Option Parsing anbieten, die Folgendes ermöglicht:

-s p1
--stage p1
-w somefolder
--workfolder somefolder
-sw p1 somefolder
-e=hello

Erlaubt auch dies (könnte unerwünscht sein):

-s--workfolder p1 somefolder
-se=hello p1
-swe=hello p1 somefolder

Sie müssen vor der Verwendung entscheiden, ob = für eine Option verwendet werden soll oder nicht. Dies dient dazu, den Code sauber zu halten (ish).

while [[ $# > 0 ]]
do
    key="$1"
    while [[ ${key+x} ]]
    do
        case $key in
            -s*|--stage)
                STAGE="$2"
                shift # option has parameter
                ;;
            -w*|--workfolder)
                workfolder="$2"
                shift # option has parameter
                ;;
            -e=*)
                EXAMPLE="${key#*=}"
                break # option has been fully handled
                ;;
            *)
                # unknown option
                echo Unknown option: $key #1>&2
                exit 10 # either this: my preferred way to handle unknown options
                break # or this: do this to signal the option has been handled (if exit isn't used)
                ;;
        esac
        # prepare for next option in this key, if any
        [[ "$key" = -? || "$key" == --* ]] && unset key || key="${key/#-?/-}"
    done
    shift # option(s) fully processed, proceed to next input argument
done
4
galmok

Beachten Sie, dass getopt(1) ein kurzer lebender Fehler von AT & T war.

getopt wurde 1984 erstellt, aber bereits 1986 begraben, weil es nicht wirklich brauchbar war.

Ein Beweis für die Tatsache, dass getopt sehr veraltet ist, ist, dass die Manpage getopt(1) immer noch "$*" Anstelle von "[email protected]" Erwähnt, das der Bourne hinzugefügt wurde Shell wurde 1986 zusammen mit der getopts(1) Shell gebaut, um mit Argumenten mit Leerzeichen im Inneren umzugehen.

Übrigens: Wenn Sie lange Optionen in Shell-Skripten analysieren möchten, ist es möglicherweise interessant zu wissen, dass die Implementierung getopt(3) von libc (Solaris) und ksh93 Beide eine einheitliche Implementierung langer Optionen hinzugefügt haben das unterstützt lange Optionen als Aliase für kurze Optionen. Dies bewirkt, dass ksh93 Und Bourne Shell Eine einheitliche Schnittstelle für lange Optionen über getopts implementieren.

Ein Beispiel für lange Optionen aus der Bourne Shell-Manpage:

getopts "f:(file)(input-file)o:(output-file)" OPTX "[email protected]"

zeigt, wie lange Options-Aliase sowohl in Bourne Shell als auch in ksh93 verwendet werden können.

Siehe die Manpage einer aktuellen Bourne Shell:

http://schillix.sourceforge.net/man/man1/bosh.1.html

und die Manpage für getopt (3) von OpenSolaris:

http://schillix.sourceforge.net/man/man3c/getopt.3c.html

und zuletzt die Manpage getopt (1), um das veraltete $ * zu überprüfen:

http://schillix.sourceforge.net/man/man1/getopt.1.html

3
schily

Angenommen, wir erstellen ein Shell-Skript mit dem Namen test_args.sh wie folgt

#!/bin/sh
until [ $# -eq 0 ]
do
  name=${1:1}; shift;
  if [[ -z "$1" || $1 == -* ]] ; then eval "export $name=true"; else eval "export $name=$1"; shift; fi  
done
echo "year=$year month=$month day=$day flag=$flag"

Nachdem wir den folgenden Befehl ausgeführt haben:

sh test_args.sh  -year 2017 -flag  -month 12 -day 22 

Die Ausgabe wäre:

year=2017 month=12 day=22 flag=true
3
John

Positions- und Flag-basierte Argumente mischen

--param = arg (gleich abgegrenzt)

Flaggen frei zwischen Positionsargumenten mischen:

./script.sh dumbo 127.0.0.1 --environment=production -q -d
./script.sh dumbo --environment=production 127.0.0.1 --quiet -d

kann mit einem ziemlich kurzen Ansatz erreicht werden:

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   param=${!pointer}
   if [[ $param != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      case $param in
         # paramter-flags with arguments
         -e=*|--environment=*) environment="${param#*=}";;
                  --another=*) another="${param#*=}";;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + 1)):$#} \
         || set -- ${@:((pointer + 1)):$#};
   fi
done

# positional remain
node_name=$1
ip_address=$2

--param arg (durch Leerzeichen getrennt)

Es ist normalerweise klarer, --flag=value- und --flag value-Stile nicht zu mischen.

./script.sh dumbo 127.0.0.1 --environment production -q -d

Dies ist ein wenig fragwürdig, aber es ist immer noch gültig

./script.sh dumbo --environment production 127.0.0.1 --quiet -d

Quelle

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   if [[ ${!pointer} != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      param=${!pointer}
      ((pointer_plus = pointer + 1))
      slice_len=1

      case $param in
         # paramter-flags with arguments
         -e|--environment) environment=${!pointer_plus}; ((slice_len++));;
                --another) another=${!pointer_plus}; ((slice_len++));;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + $slice_len)):$#} \
         || set -- ${@:((pointer + $slice_len)):$#};
   fi
done

# positional remain
node_name=$1
ip_address=$2
2
Mark Fox

Ich habe einen Bash-Helfer geschrieben, um ein Nice-Bash-Tool zu schreiben

projekt Home: https://gitlab.mbedsys.org/mbedsys/bashopts

beispiel:

#!/bin/bash -ei

# load the library
. bashopts.sh

# Enable backtrace dusplay on error
trap 'bashopts_exit_handle' ERR

# Initialize the library
bashopts_setup -n "$0" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc"

# Declare the options
bashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -r
bashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -r
bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\$first_name \$last_name"
bashopts_declare -n age -l number -d "Age" -t number
bashopts_declare -n email_list -t string -m add -l email -d "Email adress"

# Parse arguments
bashopts_parse_args "[email protected]"

# Process argument
bashopts_process_args

wird helfen:

NAME:
    ./example.sh - This is myapp tool description displayed on help message

USAGE:
    [options and commands] [-- [extra args]]

OPTIONS:
    -h,--help                          Display this help
    -n,--non-interactive true          Non interactive mode - [$bashopts_non_interactive] (type:boolean, default:false)
    -f,--first "John"                  First name - [$first_name] (type:string, default:"")
    -l,--last "Smith"                  Last name - [$last_name] (type:string, default:"")
    --display-name "John Smith"        Display name - [$display_name] (type:string, default:"$first_name $last_name")
    --number 0                         Age - [$age] (type:number, default:0)
    --email                            Email adress - [$email_list] (type:string, default:"")

genießen :)

2

Hier ist mein Ansatz - mit regexp.

  • keine getopts
  • es behandelt Block mit kurzen Parametern -qwerty
  • es behandelt kurze Parameter -q -w -e
  • es behandelt lange Optionen --qwerty
  • sie können das Attribut an eine kurze oder lange Option übergeben (wenn Sie einen Block mit kurzen Optionen verwenden, wird das Attribut der letzten Option zugeordnet.)
  • sie können Leerzeichen oder = verwenden, um Attribute bereitzustellen. Die Attribute stimmen jedoch überein, bis Sie auf einen Bindestrich + Leerzeichen als Trennzeichen stoßen. In --q=qwe ty ist qwe ty ein Attribut
  • es behandelt Mix von allem oben, also ist -o a -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute gültig

skript:

#!/usr/bin/env sh

help_menu() {
  echo "Usage:

  ${0##*/} [-h][-l FILENAME][-d]

Options:

  -h, --help
    display this help and exit

  -l, --logfile=FILENAME
    filename

  -d, --debug
    enable debug
  "
}

parse_options() {
  case $opt in
    h|help)
      help_menu
      exit
     ;;
    l|logfile)
      logfile=${attr}
      ;;
    d|debug)
      debug=true
      ;;
    *)
      echo "Unknown option: ${opt}\nRun ${0##*/} -h for help.">&2
      exit 1
  esac
}
[email protected]

until [ "$options" = "" ]; do
  if [[ $options =~ (^ *(--([a-zA-Z0-9-]+)|-([a-zA-Z0-9-]+))(( |=)(([\_\.\?\/\\a-zA-Z0-9]?[ -]?[\_\.\?a-zA-Z0-9]+)+))?(.*)|(.+)) ]]; then
    if [[ ${BASH_REMATCH[3]} ]]; then # for --option[=][attribute] or --option[=][attribute]
      opt=${BASH_REMATCH[3]}
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    Elif [[ ${BASH_REMATCH[4]} ]]; then # for block options -qwert[=][attribute] or single short option -a[=][attribute]
      pile=${BASH_REMATCH[4]}
      while (( ${#pile} > 1 )); do
        opt=${pile:0:1}
        attr=""
        pile=${pile/${pile:0:1}/}
        parse_options
      done
      opt=$pile
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    else # leftovers that don't match
      opt=${BASH_REMATCH[10]}
      options=""
    fi
    parse_options
  fi
done
2
a_z

Lösung, die nicht behandelte Argumente bewahrt. Demos enthalten.

Hier ist meine Lösung. Es ist sehr flexibel und sollte, im Gegensatz zu anderen, keine externen Pakete erfordern und die Argumente, die übrig bleiben, sauber behandeln.

Verwendung ist: ./myscript -flag flagvariable -otherflag flagvar2

Sie müssen lediglich die Zeile für gültige Flags bearbeiten. Es wird ein Bindestrich vorangestellt und alle Argumente durchsucht. Dann definiert es das nächste Argument als den Markennamen, z.

./myscript -flag flagvariable -otherflag flagvar2
echo $flag $otherflag
flagvariable flagvar2

Der Hauptcode (kurze Version, ausführlich mit Beispielen weiter unten, auch eine Version mit Fehler):

#!/usr/bin/env bash
#Shebang.io
validflags="rate time number"
count=1
for arg in [email protected]
do
    match=0
    argval=$1
    for flag in $validflags
    do
        sflag="-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=$2
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers

Die ausführliche Version mit eingebauten Echo-Demos:

#!/usr/bin/env bash
#Shebang.io
rate=30
time=30
number=30
echo "all args
[email protected]"
validflags="rate time number"
count=1
for arg in [email protected]
do
    match=0
    argval=$1
#   argval=$(echo [email protected] | cut -d ' ' -f$count)
    for flag in $validflags
    do
            sflag="-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=$2
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done

#Cleanup then restore the leftovers
echo "pre final clear args:
[email protected]"
shift $#
echo "post final clear args:
[email protected]"
set -- $leftovers
echo "all post set args:
[email protected]"
echo arg1: $1 arg2: $2

echo leftovers: $leftovers
echo rate $rate time $time number $number

Das letzte ist der Fehler, wenn ein ungültiges Argument durchlaufen wird.

#!/usr/bin/env bash
#Shebang.io
rate=30
time=30
number=30
validflags="rate time number"
count=1
for arg in [email protected]
do
    argval=$1
    match=0
        if [ "${argval:0:1}" == "-" ]
    then
        for flag in $validflags
        do
                sflag="-"$flag
            if [ "$argval" == "$sflag" ]
            then
                declare $flag=$2
                match=1
            fi
        done
        if [ "$match" == "0" ]
        then
            echo "Bad argument: $argval"
            exit 1
        fi
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers
echo rate $rate time $time number $number
echo leftovers: $leftovers

Pros: Was es tut, geht es sehr gut. Es bewahrt ungenutzte Argumente, die viele der anderen Lösungen hier nicht bieten. Außerdem können Variablen aufgerufen werden, ohne von Hand im Skript definiert zu werden. Es erlaubt auch die Vorbesetzung von Variablen, wenn kein entsprechendes Argument angegeben wird. (Siehe ausführliches Beispiel).

Nachteile: Ein einzelner komplexer Argumentstring kann nicht analysiert werden, z. -xcvf würde als einzelnes Argument verarbeitet. Sie könnten leicht etwas zusätzlichen Code in meinen schreiben, der diese Funktionalität hinzufügt. 

2
Noah

Eine andere Lösung ohne getopt [s], POSIX, alter Unix-Stil

Ähnlich wie die Lösung hat Bruno Bronosky geschrieben hier ist eine ohne die Verwendung von getopt(s).

Hauptunterscheidungsmerkmal meiner Lösung ist, dass Optionen miteinander verkettet werden können, genau wie tar -xzf foo.tar.gz gleich tar -x -z -f foo.tar.gz ist. Und wie in tar, ps usw. ist der führende Bindestrich optional für einen Block von kurzen Optionen (dies kann jedoch leicht geändert werden). Es werden auch lange Optionen unterstützt (wenn jedoch ein Block mit einer beginnt, sind zwei führende Bindestriche erforderlich).

Code mit Beispieloptionen

#!/bin/sh

echo
echo "POSIX-compliant getopt(s)-free old-style-supporting option parser from [email protected][se.unix]"
echo

print_usage() {
  echo "Usage:

  $0 {a|b|c} [ARG...]

Options:

  --aaa-0-args
  -a
    Option without arguments.

  --bbb-1-args ARG
  -b ARG
    Option with one argument.

  --ccc-2-args ARG1 ARG2
  -c ARG1 ARG2
    Option with two arguments.

" >&2
}

if [ $# -le 0 ]; then
  print_usage
  exit 1
fi

opt=
while :; do

  if [ $# -le 0 ]; then

    # no parameters remaining -> end option parsing
    break

  Elif [ ! "$opt" ]; then

    # we are at the beginning of a fresh block
    # remove optional leading hyphen and strip trailing whitespaces
    opt=$(echo "$1" | sed 's/^-\?\([a-zA-Z0-9\?-]*\)/\1/')

  fi

  # get the first character -> check whether long option
  first_chr=$(echo "$opt" | awk '{print substr($1, 1, 1)}')
  [ "$first_chr" = - ] && long_option=T || long_option=F

  # note to write the options here with a leading hyphen less
  # also do not forget to end short options with a star
  case $opt in

    -)

      # end of options
      shift
      break
      ;;

    a*|-aaa-0-args)

      echo "Option AAA activated!"
      ;;

    b*|-bbb-1-args)

      if [ "$2" ]; then
        echo "Option BBB with argument '$2' activated!"
        shift
      else
        echo "BBB parameters incomplete!" >&2
        print_usage
        exit 1
      fi
      ;;

    c*|-ccc-2-args)

      if [ "$2" ] && [ "$3" ]; then
        echo "Option CCC with arguments '$2' and '$3' activated!"
        shift 2
      else
        echo "CCC parameters incomplete!" >&2
        print_usage
        exit 1
      fi
      ;;

    h*|\?*|-help)

      print_usage
      exit 0
      ;;

    *)

      if [ "$long_option" = T ]; then
        opt=$(echo "$opt" | awk '{print substr($1, 2)}')
      else
        opt=$first_chr
      fi
      printf 'Error: Unknown option: "%s"\n' "$opt" >&2
      print_usage
      exit 1
      ;;

  esac

  if [ "$long_option" = T ]; then

    # if we had a long option then we are going to get a new block next
    shift
    opt=

  else

    # if we had a short option then just move to the next character
    opt=$(echo "$opt" | awk '{print substr($1, 2)}')

    # if block is now empty then shift to the next one
    [ "$opt" ] || shift

  fi

done

echo "Doing something..."

exit 0

Die Verwendung der Beispiele finden Sie in den Beispielen weiter unten.

Position der Optionen mit Argumenten

Für was es wert ist, sind die Optionen mit Argumenten nicht die letzten (nur lange Optionen müssen sein). Während also z. In tar (zumindest in einigen Implementierungen) müssen die f-Optionen zuletzt sein, da der Dateiname folgt (tar xzf bar.tar.gz funktioniert, aber tar xfz bar.tar.gz nicht). Dies ist hier nicht der Fall (siehe die späteren Beispiele).

Mehrere Optionen mit Argumenten

Als weiterer Bonus werden die Optionsparameter in der Reihenfolge der Optionen von den Parametern mit den erforderlichen Optionen verbraucht. Sehen Sie sich die Ausgabe meines Skripts hier mit der Befehlszeile abc X Y Z (oder -abc X Y Z) an:

Option AAA activated!
Option BBB with argument 'X' activated!
Option CCC with arguments 'Y' and 'Z' activated!

Lange Optionen auch verkettet

Sie können auch lange Optionen im Optionsblock haben, da diese zuletzt im Block vorkommen. Die folgenden Befehlszeilen sind also alle gleich (einschließlich der Reihenfolge, in der die Optionen und ihre Argumente verarbeitet werden):

  • -cba Z Y X
  • cba Z Y X
  • -cb-aaa-0-args Z Y X
  • -c-bbb-1-args Z Y X -a
  • --ccc-2-args Z Y -ba X
  • c Z Y b X a
  • -c Z Y -b X -a
  • --ccc-2-args Z Y --bbb-1-args X --aaa-0-args

All dies führt zu:

Option CCC with arguments 'Z' and 'Y' activated!
Option BBB with argument 'X' activated!
Option AAA activated!
Doing something...

Nicht in dieser Lösung

Optionale Argumente

Optionen mit optionalen Argumenten sollten mit etwas Arbeit möglich sein, z. indem wir uns freuen, ob es einen Block ohne Bindestrich gibt; Der Benutzer muss dann vor jedem Block einen Bindestrich setzen, der einem Block mit einem Parameter mit einem optionalen Parameter folgt. Vielleicht ist dies zu kompliziert, um mit dem Benutzer zu kommunizieren, so dass Sie in diesem Fall besser einen führenden Bindestrich benötigen.

Noch komplizierter wird es mit mehreren möglichen Parametern. Ich würde davon abraten, die Optionen zu versuchen, klug zu sein, indem ich herausfinde, ob das Argument ein Argument für es ist oder nicht (z. B. bei einer Option wird nur eine Zahl als optionales Argument verwendet), da dies in der Zukunft brechen könnte.

Ich persönlich bevorzuge zusätzliche Optionen anstelle von optionalen Argumenten.

Mit einem Gleichheitszeichen eingeführte Optionsargumente

Genau wie bei optionalen Argumenten bin ich kein Fan davon (BTW, gibt es einen Thread, um die Vor-/Nachteile verschiedener Parameterstile zu diskutieren?), Aber wenn Sie dies wollen, könnten Sie es wahrscheinlich selbst wie bei http implementieren: //mywiki.wooledge.org/BashFAQ/035#Manual_loop mit einer --long-with-arg=?* case-Anweisung und entfernt dann das Gleichheitszeichen (dies ist übrigens die Site, die besagt, dass das Erstellen von Parameterketten mit etwas Aufwand möglich ist, aber "es [als] belassen) eine Übung für den Leser ", die mich dazu brachte, sie an ihrem Wort zu nehmen, aber ich habe bei Null angefangen).

Weitere Hinweise

POSIX-kompatibel, funktioniert auch in alten Busybox-Setups, mit denen ich mich befassen musste (z. B. cut, head und getopts).

1
phk

Dies kann auch hilfreich sein, um zu wissen, dass Sie einen Wert festlegen können und falls jemand Eingaben vornimmt, den Standardwert mit diesem Wert überschreiben. 

myscript.sh -f ./serverlist.txt oder einfach ./myscript.sh (und es sind Standardeinstellungen erforderlich)

    #!/bin/bash
    # --- set the value, if there is inputs, override the defaults.

    HOME_FOLDER="${HOME}/owned_id_checker"
    SERVER_FILE_LIST="${HOME_FOLDER}/server_list.txt"

    while [[ $# > 1 ]]
    do
    key="$1"
    shift

    case $key in
        -i|--inputlist)
        SERVER_FILE_LIST="$1"
        shift
        ;;
    esac
    done


    echo "SERVER LIST   = ${SERVER_FILE_LIST}"
1
Mike Q

Hier ist meine verbesserte Lösung von Bruno Bronoskys Antwort mit variablen Arrays.

sie können Parameterpositionen mischen und erhalten ein Parameterarray, das die Reihenfolge ohne Optionen beibehält

#!/bin/bash

echo [email protected]

PARAMS=()
SOFT=0
SKIP=()
for i in "[email protected]"
do
case $i in
    -n=*|--skip=*)
    SKIP+=("${i#*=}")
    ;;
    -s|--soft)
    SOFT=1
    ;;
    *)
        # unknown option
        PARAMS+=("$i")
    ;;
esac
done
echo "SKIP            = ${SKIP[@]}"
echo "SOFT            = $SOFT"
    echo "Parameters:"
    echo ${PARAMS[@]}

Wird zum Beispiel ausgegeben:

$ ./test.sh parameter -s somefile --skip=.c --skip=.obj
parameter -s somefile --skip=.c --skip=.obj
SKIP            = .c .obj
SOFT            = 1
Parameters:
parameter somefile
1
Masadow

Um die Antwort von @ bruno-bronosky zu erweitern, habe ich einen "Präprozessor" hinzugefügt, um einige gängige Formatierungen zu handhaben:

  • Erweitert --longopt=val zu --longopt val
  • Erweitert -xyz zu -x -y -z
  • Unterstützt --, um das Ende von Flags anzuzeigen
  • Zeigt einen Fehler für unerwartete Optionen an
  • Kompakter und einfach zu lesender Optionsschalter
#!/bin/bash

# Report usage
usage() {
  echo "Usage:"
  echo "$(basename $0) [options] [--] [file1, ...]"

  # Optionally exit with a status code
  if [ -n "$1" ]; then
    exit "$1"
  fi
}

invalid() {
  echo "ERROR: Unrecognized argument: $1" >&2
  usage 1
}

# Pre-process options to:
# - expand -xyz into -x -y -z
# - expand --longopt=arg into --longopt arg
ARGV=()
END_OF_OPT=
while [[ $# -gt 0 ]]; do
  arg="$1"; shift
  case "${END_OF_OPT}${arg}" in
    --) ARGV+=("$arg"); END_OF_OPT=1 ;;
    --*=*)ARGV+=("${arg%%=*}" "${arg#*=}") ;;
    --*) ARGV+=("$arg"); END_OF_OPT=1 ;;
    -*) for i in $(seq 2 ${#arg}); do ARGV+=("-${arg:i-1:1}"); done ;;
    *) ARGV+=("$arg") ;;
  esac
done

# Apply pre-processed options
set -- "${ARGV[@]}"

# Parse options
END_OF_OPT=
POSITIONAL=()
while [[ $# -gt 0 ]]; do
  case "${END_OF_OPT}${1}" in
    -h|--help)      usage 0 ;;
    -p|--password)  shift; PASSWORD="$1" ;;
    -u|--username)  shift; USERNAME="$1" ;;
    -n|--name)      shift; names+=("$1") ;;
    -q|--quiet)     QUIET=1 ;;
    -C|--copy)      COPY=1 ;;
    -N|--notify)    NOTIFY=1 ;;
    --stdin)        READ_STDIN=1 ;;
    --)             END_OF_OPT=1 ;;
    -*)             invalid "$1" ;;
    *)              POSITIONAL+=("$1") ;;
  esac
  shift
done

# Restore positional parameters
set -- "${POSITIONAL[@]}"
1
jchook

Dieses Beispiel zeigt, wie Sie mit getopt und eval und HEREDOC und shift kurze und lange Parameter mit und ohne folgenden erforderlichen Wert behandeln. Auch die switch/case-Anweisung ist kurz und leicht zu folgen.

#!/usr/bin/env bash

# usage function
function usage()
{
   cat << HEREDOC

   Usage: $progname [--num NUM] [--time TIME_STR] [--verbose] [--dry-run]

   optional arguments:
     -h, --help           show this help message and exit
     -n, --num NUM        pass in a number
     -t, --time TIME_STR  pass in a time string
     -v, --verbose        increase the verbosity of the bash script
     --dry-run            do a dry run, don't change any files

HEREDOC
}  

# initialize variables
progname=$(basename $0)
verbose=0
dryrun=0
num_str=
time_str=

# use getopt and store the output into $OPTS
# note the use of -o for the short options, --long for the long name options
# and a : for any option that takes a parameter
OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "[email protected]")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; usage; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  # uncomment the next line to see how shift is working
  # echo "\$1:\"$1\" \$2:\"$2\""
  case "$1" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str="$2"; shift 2 ;;
    -t | --time ) time_str="$2"; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

if (( $verbose > 0 )); then

   # print out all the parameters we read in
   cat <<-EOM
   num=$num_str
   time=$time_str
   verbose=$verbose
   dryrun=$dryrun
EOM
fi

# The rest of your script below

Die wichtigsten Zeilen des obigen Skripts lauten wie folgt:

OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "[email protected]")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  case "$1" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str="$2"; shift 2 ;;
    -t | --time ) time_str="$2"; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

Kurz, auf den Punkt, lesbar und handhabt so ziemlich alles (IMHO).

Hoffe das hilft jemandem.

1
phyatt

Verwenden Sie das Modul "Argumente" aus bash-modules

Beispiel:

#!/bin/bash
. import.sh log arguments

NAME="world"

parse_arguments "-n|--name)NAME;S" -- "[email protected]" || {
  error "Cannot parse command line."
  exit 1
}

info "Hello, $NAME!"

Ich möchte mein Projekt einreichen: https://github.com/flyingangel/argparser

source argparser.sh
parse_args "[email protected]"

So einfach ist das. Die Umgebung wird mit Variablen gefüllt, die denselben Namen wie die Argumente haben

1
Thanh Trung

Die beste Antwort auf diese Frage schien ein wenig fehlerhaft zu sein, als ich es ausprobierte - hier ist meine Lösung, die ich als robuster empfunden habe:

boolean_arg=""
arg_with_value=""

while [[ $# -gt 0 ]]
do
key="$1"
case $key in
    -b|--boolean-arg)
    boolean_arg=true
    shift
    ;;
    -a|--arg-with-value)
    arg_with_value="$2"
    shift
    shift
    ;;
    -*)
    echo "Unknown option: $1"
    exit 1
    ;;
    *)
    arg_num=$(( $arg_num + 1 ))
    case $arg_num in
        1)
        first_normal_arg="$1"
        shift
        ;;
        2)
        second_normal_arg="$1"
        shift
        ;;
        *)
        bad_args=TRUE
    esac
    ;;
esac
done

# Handy to have this here when adding arguments to
# see if they're working. Just edit the '0' to be '1'.
if [[ 0 == 1 ]]; then
    echo "first_normal_arg: $first_normal_arg"
    echo "second_normal_arg: $second_normal_arg"
    echo "boolean_arg: $boolean_arg"
    echo "arg_with_value: $arg_with_value"
    exit 0
fi

if [[ $bad_args == TRUE || $arg_num < 2 ]]; then
    echo "Usage: $(basename "$0") <first-normal-arg> <second-normal-arg> [--boolean-arg] [--arg-with-value VALUE]"
    exit 1
fi
1
Daniel Bigham

Einfach und leicht zu ändern, Parameter können in beliebiger Reihenfolge sein. Dies kann geändert werden, um Parameter in beliebiger Form (-a, --a, a usw.) zu übernehmen.

for arg in "[email protected]"
do
   key=$(echo $arg | cut -f1 -d=)`
   value=$(echo $arg | cut -f2 -d=)`
   case "$key" in
        name|-name)      read_name=$value;;
        id|-id)          read_id=$value;;
        *)               echo "I dont know what to do with this"
   ease
done
0
terijo001