command-line shell - Wie parse ich Befehlszeilenargumente in Bash?




long getopts (24)

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 zu analysieren, so dass in jedem Fall (oder einer Kombination der beiden) $v , $f und $d alle auf true und $outFile gleich /fizz/someOtherFile ?


Answers

Positions-und Flag-basierte Argumente mischen

--param = arg (ist abgegrenzt)

Frei mixende Flags zwischen Positionsargumenten:

./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 prägnanten 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 (Leerzeichen getrennt)

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

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

Dies ist ein wenig heikel zu lesen, aber 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

Meine Antwort basiert größtenteils auf der 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 -arg 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


Ich gebe dir die Funktion parse_params , die params parsen:

  1. Ohne die globale Reichweite zu verschmutzen.
  2. Mühelos kehrt zu Ihnen bereit, Variablen zu verwenden, damit Sie weitere Logik auf ihnen erstellen konnten
  3. Die Anzahl der Bindestriche vor den Parametern spielt keine Rolle ( --all gleich -all gleich all=all )

Das folgende Skript ist eine Demo zum Kopieren und Einfügen. Sehen show_use Funktion show_use an, um zu verstehen, wie parse_params .

Einschränkungen:

  1. Unterstützt keine durch Leerzeichen begrenzten Parameter ( -d 1 )
  2. Param-Namen verlieren Bindestriche, so dass --any-param und -anyparam gleichwertig sind
  3. eval $(parse_params "[email protected]") muss innerhalb der bash Funktion verwendet werden ( funktioniert nicht im globalen Bereich)
#!/bin/bash

# Universal Bash parameter parsing
# Parse equals separated params into named local variables
# Standalone named parameter value will equal param name (--force creates variable $force=="force")
# Parses multi-valued named params into array (--path=path1 --path=path2 creates ${path[*]} array)
# Parses un-named params into ${ARGV[*]} array
# @author Oleksii Chekulaiev
# @version v1.2 (Aug-24-2017)
parse_params ()
{
    local existing_named
    local ARGV=()
    echo "local ARGV=(); "
    while [[ "$1" != "" ]]; do
        # If equals delimited named parameter
        if [[ "$1" =~ ^..*=..* ]]; then
            # 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//\-}
            # 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//\-}
            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]}"
    echo "${ARGV[1]}"
    echo "$anyparam"
    echo "$k"
    echo "${multivalue[0]}"
    echo "${multivalue[1]}"
    [[ "$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

Ich habe das Argbash gefunden, portierbares Parsen in Skripten zu schreiben, was so frustrierend ist, dass ich Argbash geschrieben Argbash - einen FOSS-Code-Generator, der den Parsing-Code für Ihr Skript generieren kann und einige nette Features hat:

https://argbash.io


Dies kann auch nützlich sein, um zu wissen, dass Sie einen Wert festlegen können und wenn jemand eine Eingabe bereitstellt, überschreiben Sie den Standardwert mit diesem Wert.

myscript.sh -f ./serverlist.txt oder einfach ./myscript.sh (und es nimmt die Standardeinstellungen an)

    #!/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}"

Benutze 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!"

Keine Antwort erwähnt verbesserte getopt . Und die Antwort mit der höchsten Wahl ist irreführend: Sie ignoriert kurze Optionen vom -⁠vfd (vom OP angefordert), Optionen nach Positionsargumenten (auch vom OP angefordert) und ignoriert Parsing-Fehler. Stattdessen:

  • Verwenden Sie erweitertes getopt von util-linux oder früher GNU glibc . 1
  • Es funktioniert mit getopt_long() der C-Funktion von GNU glibc.
  • Hat alle nützlichen Unterscheidungsmerkmale (die anderen haben sie nicht):
    • behandelt Leerzeichen, zitiert Zeichen und sogar binär in Argumenten 2
    • Es kann Optionen am Ende behandeln: script.sh -o outFile file1 file2 -v
    • erlaubt lange Optionen script.sh --outfile=fileOut --infile fileIn : script.sh --outfile=fileOut --infile fileIn
  • Ist schon so alt 3 dass kein GNU System das vermisst (; zB hat irgendein Linux es).
  • Sie können auf seine Existenz prüfen 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 dem folgenden myscript

#!/bin/bash

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

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

# -temporarily store output to be able to check for errors
# -e.g. use “--options” parameter by name to activate quoting/enhanced mode
# -pass arguments only via   -- "[email protected]"   to separate them correctly
PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTIONS --name "$0" -- "[email protected]")
if [[ $? -ne 0 ]]; then
    # e.g. $? == 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"

# 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; Unter OS X versuche brauen Gnu-getopt installieren
2 Die Konventionen von POSIX exec() haben keine zuverlässige Möglichkeit, binäre NULL in Befehlszeilenargumenten zu übergeben. Diese Bytes beenden das Argument vorzeitig
3 erste Version im Jahr 1997 oder früher veröffentlicht (ich habe es nur bis 1997 zurückverfolgt)


Erweiternd auf die ausgezeichnete Antwort von @ guneys, hier ist ein Tweak, der Benutzer verwenden kann, welche Syntax sie bevorzugen, z

command -x=myfilename.ext --another_switch 

vs

command -x myfilename.ext --another_switch

Das heißt, der Gleichheitsfaktor kann durch Leerzeichen ersetzt werden.

Diese "unscharfe Interpretation" mag Ihnen nicht gefallen, aber wenn Sie Skripte erstellen, die mit anderen Dienstprogrammen austauschbar sind (wie es bei mir der Fall ist, der mit ffmpeg arbeiten 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

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

  • behandelt -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

Here is my improved solution of Bruno Bronosky's answer using variable arrays.

it lets you mix parameters position and give you a parameter array preserving the order without the options

#!/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[@]}

Will output for example:

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

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 [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

So mache ich in einer Funktion, um zu vermeiden, dass Getopts gleichzeitig irgendwo im Stapel höher laufen:

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
...
}

Ich möchte meine Version des Optionsparsing anbieten, die folgendes ermöglicht:

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

Ermöglicht 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 ist, um 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

Solution that preserves unhandled arguments. Demos Included.

Here is my solution. It is VERY flexible and unlike others, shouldn't require external packages and handles leftover arguments cleanly.

Usage is: ./myscript -flag flagvariable -otherflag flagvar2

All you have to do is edit the validflags line. It prepends a hyphen and searches all arguments. It then defines the next argument as the flag name eg

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

The main code (short version, verbose with examples further down, also a version with erroring out):

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

The verbose version with built in 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

Final one, this one errors out if an invalid -argument is passed through.

#!/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: What it does, it handles very well. It preserves unused arguments which a lot of the other solutions here don't. It also allows for variables to be called without being defined by hand in the script. It also allows prepopulation of variables if no corresponding argument is given. (See verbose example).

Cons: Can't parse a single complex arg string eg -xcvf would process as a single argument. You could somewhat easily write additional code into mine that adds this functionality though.


Assume we create a shell script named test_args.sh as follow

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

After we run the following command:

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

The output would be:

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

Here is my approach - using regexp.

  • no getopts
  • it handles block of short parameters -qwerty
  • it handles short parameters -q -w -e
  • it handles long options --qwerty
  • you can pass attribute to short or long option (if you are using block of short options, attribute is attached to the last option)
  • you can use spaces or = to provide attributes, but attribute matches until encountering hyphen+space "delimiter", so in --q=qwe ty qwe ty is one attribute
  • it handles mix of all above so -oa -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute is valid

script:

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

Getopts funktioniert gut, wenn Sie es installiert haben und # 2 Sie es auf der gleichen Plattform ausführen möchten. OSX und Linux (zum Beispiel) verhalten sich in dieser Hinsicht anders.

Hier ist eine (nicht getopts) Lösung, die gleichwertige, nicht gleichwertige und boolesche Flags unterstützt. Zum Beispiel könnten Sie Ihr Skript auf diese 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

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

getopt wurde 1984 erstellt, aber schon 1986 vergraben, weil es nicht wirklich brauchbar war.

Ein Beweis für die Tatsache, dass getopt sehr veraltet ist, ist, dass die getopt(1) man page immer noch "$*" anstelle von "[email protected]" , das 1986 zusammen mit der getopts(1) Shell in die Bourne Shell eingefügt wurde um mit Argumenten mit Leerzeichen innerhalb umzugehen.

BTW: Wenn Sie lange Parts in Shell-Skripten analysieren wollen, ist es vielleicht interessant zu wissen, dass die getopt(3) aus libc (Solaris) und ksh93 eine einheitliche lange Option implementiert hat, die lange Optionen als Aliase unterstützt Optionen. Dies bewirkt, dass ksh93 und die Bourne Shell eine einheitliche Schnittstelle für lange Optionen über getopts .

Ein Beispiel für lange Optionen, die von der Bourne-Shell-Manpage übernommen wurden:

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

zeigt an, wie lange Option Aliase in Bourne Shell und ksh93 verwendet werden können.

Siehe die man-Seite 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 getopt (1) man-Seite, um veraltete $ * zu überprüfen:

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


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

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 entweder -a, -b, -c oder -d zulässig sind, aber auf -c ein Argument folgt (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" zerlegt die Optionen und setzt jedes in ein eigenes Argument. Es fügte auch "-" hinzu.


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 von ${i#*=} suchen Sie in diesem Handbuch nach "Substring Removal". Es ist funktional äquivalent zu `sed 's/[^=]*=//' <<< "$i"` was einen unnötigen Subprozess oder `echo "$i" | sed 's/[^=]*=//'` aufruft `echo "$i" | sed 's/[^=]*=//'` was zwei unnötige Subprozesse aufruft.


Ich denke, das ist einfach genug zu verwenden:

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

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

I have write a bash helper to write a nice bash tool

project 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

will give help:

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

enjoy :)


Ich ziehe es vor, geschweifte Klammern ${} zu verwenden, um eine Variable in einem String zu erweitern:

foo="Hello"
foo="${foo} World"
echo $foo
> Hello World

Geschweifte Klammern passen zur fortlaufenden Saitenanwendung:

foo="Hello"
foo="${foo}World"
echo $foo
> HelloWorld

Ansonsten foo = "$fooWorld" nicht.





bash command-line scripting arguments