Hoe parseer ik opdrachtregelargumenten in Bash?

Zeg, ik heb een script dat wordt aangeroepen met deze regel:

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

of deze:

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

Wat is de geaccepteerde manier om dit zo te ontleden dat in elk geval (of een combinatie van beide) $v, $fen $dworden allemaal ingesteld op trueen $outFileis gelijk aan /fizz/someOtherFile?


Antwoord 1, autoriteit 100%

Bash Space-Separated (bijv. --option argument)

cat >/tmp/demo-space-separated.sh <<'EOF'
#!/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
EOF
chmod +x /tmp/demo-space-separated.sh
/tmp/demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts
Uitvoer van kopieerpackering van het blok boven
FILE EXTENSION  = conf
SEARCH PATH     = /etc
LIBRARY PATH    = /usr/lib
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com
Gebruik
demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts

bash is gelijk aan gescheiden (bijvoorbeeld --option=argument)

cat >/tmp/demo-equals-separated.sh <<'EOF'
#!/bin/bash
for i in "$@"; do
  case $i in
    -e=*|--extension=*)
      EXTENSION="${i#*=}"
      shift # past argument=value
      ;;
    -s=*|--searchpath=*)
      SEARCHPATH="${i#*=}"
      shift # past argument=value
      ;;
    -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 "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 $1
fi
EOF
chmod +x /tmp/demo-equals-separated.sh
/tmp/demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts
Uitvoer van het kopiëren en plakken van het bovenstaande blok
FILE EXTENSION  = conf
SEARCH PATH     = /etc
LIBRARY PATH    = /usr/lib
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com
Gebruik
demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

Om ${i#*=}beter te begrijpen, zoek je naar “Substring Removal” in deze handleiding. Het is functioneel equivalent aan `sed 's/[^=]*=//' <<< "$i"`die een onnodig subproces of `echo "$i" | sed 's/[^=]*=//'`die tweeonnodige subprocessen aanroept.


Bash gebruiken met getopt[s]

getopt(1) beperkingen (oudere, relatief recente getoptversies):

  • kan geen argumenten aan die lege tekenreeksen zijn
  • kan niet omgaan met argumenten met ingesloten witruimte

Recente getopt-versies hebben deze beperkingen niet. Zie deze docsvoor meer informatie.


POSIX wint

Bovendien bieden de POSIX-shell en andere getoptsdie deze beperkingen niet hebben. Ik heb een simplistisch getoptsvoorbeeld toegevoegd.

cat >/tmp/demo-getopts.sh <<'EOF'
#!/bin/sh
# A POSIX variable
OPTIND=1         # Reset in case getopts has been used previously in the shell.
# Initialize our own variables:
output_file=""
verbose=0
while getopts "h?vf:" opt; do
  case "$opt" in
    h|\?)
      show_help
      exit 0
      ;;
    v)  verbose=1
      ;;
    f)  output_file=$OPTARG
      ;;
  esac
done
shift $((OPTIND-1))
[ "${1:- }" = "--" ] && shift
echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"
EOF
chmod +x /tmp/demo-getopts.sh
/tmp/demo-getopts.sh -vf /etc/hosts foo bar
Uitvoer van het kopiëren en plakken van het bovenstaande blok
verbose=1, output_file='/etc/hosts', Leftovers: foo bar
Gebruik
demo-getopts.sh -vf /etc/hosts foo bar

De voordelen van getoptszijn:

  1. Het is draagbaarder en werkt in andere shells zoals dash.
  2. Het kan automatisch meerdere enkele opties aan, zoals -vf filenameop de typische Unix-manier.

Het nadeel van getoptsis dat het alleen korte opties (-h, niet --help) aankan zonder aanvullende code.

Er is een getopts-tutorialwaarin wordt uitgelegd wat alle syntaxis en variabelen betekenen. In bash is er ook help getopts, wat informatief kan zijn.


Antwoord 2, autoriteit 21%

Geen antwoord toont verbeterde getopt. En het antwoord met de meeste stemmenis misleidend:het negeert ofwel -⁠vfdstijl korte opties (gevraagd door het OP) of opties na positionele argumenten (ook gevraagd door het OP); en het negeert parseerfouten. In plaats daarvan:

  • Gebruik verbeterde getoptvan util-linux of voorheen GNU glibc.1
  • Het werkt met getopt_long()de C-functie van GNU glibc.
  • geen enkele andere oplossing op deze pagina kan dit allemaal:
    • verwerkt spaties, citeert tekens en zelfs binaire getallen in argumenten2(niet-verbeterde getoptkan dit niet)
    • het kan aan het einde opties aan: script.sh -o outFile file1 file2 -v(getoptsdoet dit niet)
    • staat =-stijl lange opties toe: script.sh --outfile=fileOut --infile fileIn(beide toestaan is lang als ze zichzelf parseren)
    • maakt gecombineerde korte opties mogelijk, b.v. -vfd(echt werk als het zelf parseert)
    • maakt het mogelijk om optie-argumenten aan te raken, bijv. -oOutfileof -vfdoOutfile
  • Is al zo oud3dat geen enkel GNU-systeem dit mist (bijvoorbeeld elke Linux heeft het).
  • Je kunt het bestaan ervan testen met: getopt --test→ return value 4.
  • Andere getoptof shell-built-in getoptszijn van beperkt nut.

De volgende oproepen

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

allemaal terug

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

met het volgende myscript

#!/bin/bash
# More safety, by turning some bugs into errors.
# Without `errexit` you don’t need ! and can replace
# PIPESTATUS with a simple $?, but I don’t do that.
set -o errexit -o pipefail -o noclobber -o nounset
# -allow a command to fail with !’s side effect on errexit
# -use return value from ${PIPESTATUS[0]}, because ! hosed $?
! getopt --test > /dev/null 
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
    echo 'I’m sorry, `getopt --test` failed in this environment.'
    exit 1
fi
OPTIONS=dfo:v
LONGOPTS=debug,force,output:,verbose
# -regarding ! and PIPESTATUS see above
# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via   -- "$@"   to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
    # e.g. return value is 1
    #  then getopt has complained about wrong arguments to stdout
    exit 2
fi
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED"
d=n f=n v=n outFile=-
# now enjoy the options in order and nicely split until we see --
while true; do
    case "$1" in
        -d|--debug)
            d=y
            shift
            ;;
        -f|--force)
            f=y
            shift
            ;;
        -v|--verbose)
            v=y
            shift
            ;;
        -o|--output)
            outFile="$2"
            shift 2
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "Programming error"
            exit 3
            ;;
    esac
done
# handle non-option arguments
if [[ $# -ne 1 ]]; then
    echo "$0: A single input file is required."
    exit 4
fi
echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"

1verbeterde getopt is beschikbaar op de meeste “bash-systemen”, inclusief Cygwin; probeer op OS X brew install gnu-getoptof sudo port install getopt
2de POSIX exec()-conventies hebben geen betrouwbare manier om binaire NULL door te geven in opdrachtregelargumenten; die bytes beëindigen het argument voortijdig
3eerste versie uitgebracht in 1997 of eerder (ik volgde het alleen terug naar 1997)


Antwoord 3, autoriteit 9%

deploy.sh

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

Gebruik:

./deploy.sh -t dev -u
# OR:
./deploy.sh --target dev --uglify

Antwoord 4, autoriteit 5%

Van digitalpeer.commet kleine aanpassingen:

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

Om beter te begrijpen ${i#*=}zoeken naar “substring verwijderen” in Deze gids . Het is functioneel equivalent aan `sed 's/[^=]*=//' <<< "$i"`die een onnodige subprocess of `echo "$i" | sed 's/[^=]*=//'`die twee noemt onnodige subprocessen.


5, Autoriteit 4%

getopt()/ getopts()is een goede optie. Gekopieerd van hier :

Het eenvoudige gebruik van “Getopt” wordt weergegeven in dit Mini-Script:

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

Wat we hebben gezegd, is dat een van -een,
-B, -C of -D is toegestaan, maar dat -c wordt gevolgd door een argument (de “C:” zegt dat).

Als we dit “g” noemen en het uitproberen:

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

We beginnen met twee argumenten, en
“Getopt” breekt de opties uit en
zet elk in zijn eigen argument. Het ook
toegevoegd “-“.


6, Autoriteit 4%

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

Deze oplossing:

  • verwerkt -n argen --name=arg
  • laat argumenten aan het einde toe
  • toont normale fouten als iets verkeerd is gespeld
  • compatibel, gebruikt geen bashisms
  • leesbaar, vereist geen status in een lus

Antwoord 7

Ik heb de eerdere antwoorden als uitgangspunt gebruikt om mijn oude adhoc param-parsing op te ruimen. Ik heb vervolgens de volgende sjablooncode gerefactored. Het behandelt zowel lange als korte parameters, waarbij = of spatiegescheiden argumenten worden gebruikt, evenals meerdere korte parameters die bij elkaar zijn gegroepeerd. Ten slotte voegt het alle niet-param-argumenten opnieuw in de $1,$2..-variabelen in.

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

8

Ik heb ontdekt dat het schrijven van draagbare parsing in scripts zo frustrerend is dat ik Argbash– een FOSS-codegenerator die de code voor het parseren van argumenten voor uw script kan genereren, plus een aantal leuke functies:

https://argbash.io


Antwoord 9

# 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

Hierdoor kunt u zowel door spaties gescheiden opties/waarden als gelijk gedefinieerde waarden hebben.

U kunt uw script dus uitvoeren met:

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

evenals:

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

en beide zouden hetzelfde eindresultaat moeten hebben.

PRO’S:

  • Staat zowel -arg=value als -arg value toe

  • Werkt met elke arg-naam die je in bash kunt gebruiken

    • Betekenis -a of -arg of –arg of -a-r-g of wat dan ook
  • Puur bash. Het is niet nodig om getopt of getopts te leren/gebruiken

NADELEN:

  • Kan argumenten niet combineren

    • Betekenis nee -abc. Je moet -a -b -c
    • . doen


Antwoord 10

Dit voorbeeld laat zien hoe u getopten evalen HEREDOCen shiftgebruikt om korte en lange parameters te verwerken met en zonder een vereiste waarde die volgt. Ook de switch/case-verklaring is beknopt en gemakkelijk te volgen.

#!/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, dont 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" -- "$@")
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

De belangrijkste regels van het bovenstaande script zijn deze:

OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@")
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

Kort, to the point, leesbaar en regelt zo ongeveer alles (IMHO).

Ik hoop dat het iemand helpt.


Antwoord 11

Als u scripts maakt die uitwisselbaar zijn met andere hulpprogramma’s, kan onderstaande flexibiliteit nuttig zijn.

Ofwel:

command -x=myfilename.ext --another_switch 

Of:

command -x myfilename.ext --another_switch

Hier is de code:

STD_IN=0
prefix=""
key=""
value=""
for keyValue in "$@"
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

Antwoord 12

Voortbouwend op het antwoord van @bruno-bronosky, heb ik een “preprocessor” toegevoegd om een aantal veelvoorkomende opmaak te verwerken:

  • Breidt --longopt=valuit tot --longopt val
  • Breidt -xyzuit tot -x -y -z
  • Ondersteunt --om het einde van vlaggen aan te geven
  • Toont een fout voor onverwachte opties
  • Compacte en gemakkelijk leesbare optieschakelaar
#!/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") ;;
    -*) 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[@]}"

13

Ik denk dat deze eenvoudig genoeg is om te gebruiken:

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

Aanvoersvoorbeeld:

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

14

Getopts Works Great If # 1 Je hebt het geïnstalleerd en # 2 Je bent van plan het op hetzelfde platform te laten rennen. OSX en Linux (bijvoorbeeld) gedragen zich in dit opzicht anders.

Hier is een (niet-getopts) oplossing die gelijk is aan, niet-gelijken en Booleaanse vlaggen. U kunt bijvoorbeeld uw script op deze manier uitvoeren:

./script --arg1=value1 --arg2 value2 --shouldClean
# parse the arguments.
COUNTER=0
ARGS=("$@")
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

Antwoord 15

Nog een andere optie-parser (generator)

Een elegante optie-parser voor shell-scripts (volledige ondersteuning voor alle POSIX-shells)
https://github.com/ko1nksm/getoptions(Update: v3.3.0 uitgebracht op 2021-05 -02)

getoptionsis een nieuwe optie-parser (generator) geschreven in POSIX-compatibel shellscript en uitgebracht in augustus 2020. Het is voor degenen die de POSIX / GNU-stijloptiesyntaxis in uw shell willen ondersteunen scripts.

De ondersteunde syntaxis zijn -a, +a, -abc, -vvv, -p VALUE, -pVALUE, --flag, --no-flag, --with-flag, --without-flag, --param VALUE, --param=VALUE, --option[=VALUE], --no-option--.

Het ondersteunt subcommando’s, validatie, verkorte opties en het automatisch genereren van hulp. En werkt met alle POSIX-shells (dash 0.5.4+, bash 2.03+, ksh88+, mksh R28+, zsh 3.1.9+, yash 2.29+, busybox ash 1.1.3+, etc).

#!/bin/sh
VERSION="0.1"
parser_definition() {
  setup   REST help:usage -- "Usage: example.sh [options]... [arguments]..." ''
  msg -- 'Options:'
  flag    FLAG    -f --flag                 -- "takes no arguments"
  param   PARAM   -p --param                -- "takes one argument"
  option  OPTION  -o --option on:"default"  -- "takes one optional argument"
  disp    :usage  -h --help
  disp    VERSION    --version
}
eval "$(getoptions parser_definition) exit 1"
echo "FLAG: $FLAG, PARAM: $PARAM, OPTION: $OPTION"
printf '%s\n' "$@" # rest arguments

Het parseert de volgende argumenten:

example.sh -f --flag -p VALUE --param VALUE -o --option -oVALUE --option=VALUE 1 2 3

En automatisch genereren van hulp.

$ example.sh --help
Usage: example.sh [options]... [arguments]...
Options:
  -f, --flag                  takes no arguments
  -p, --param PARAM           takes one argument
  -o, --option[=OPTION]       takes one optional argument
  -h, --help
      --version

Het is ook een optie-parsergenerator, die de volgende eenvoudige optie-parseercode genereert. Als u de gegenereerde code gebruikt, heeft u geen getoptionsnodig. Behaal echte draagbaarheid en geen afhankelijkheid.

FLAG=''
PARAM=''
OPTION=''
REST=''
getoptions_parse() {
  OPTIND=$(($#+1))
  while OPTARG= && [ $# -gt 0 ]; do
    case $1 in
      --?*=*) OPTARG=$1; shift
        eval 'set -- "${OPTARG%%\=*}" "${OPTARG#*\=}"' ${1+'"$@"'}
        ;;
      --no-*|--without-*) unset OPTARG ;;
      -[po]?*) OPTARG=$1; shift
        eval 'set -- "${OPTARG%"${OPTARG#??}"}" "${OPTARG#??}"' ${1+'"$@"'}
        ;;
      -[fh]?*) OPTARG=$1; shift
        eval 'set -- "${OPTARG%"${OPTARG#??}"}" -"${OPTARG#??}"' ${1+'"$@"'}
        OPTARG= ;;
    esac
    case $1 in
      '-f'|'--flag')
        [ "${OPTARG:- }" ] && OPTARG=${OPTARG#*\=} && set "noarg" "$1" && break
        eval '[ ${OPTARG+x} ] &&:' && OPTARG='1' || OPTARG=''
        FLAG="$OPTARG"
        ;;
      '-p'|'--param')
        [ $# -le 1 ] && set "required" "$1" && break
        OPTARG=$2
        PARAM="$OPTARG"
        shift ;;
      '-o'|'--option')
        set -- "$1" "$@"
        [ ${OPTARG+x} ] && {
          case $1 in --no-*|--without-*) set "noarg" "${1%%\=*}"; break; esac
          [ "${OPTARG:- }" ] && { shift; OPTARG=$2; } || OPTARG='default'
        } || OPTARG=''
        OPTION="$OPTARG"
        shift ;;
      '-h'|'--help')
        usage
        exit 0 ;;
      '--version')
        echo "${VERSION}"
        exit 0 ;;
      --)
        shift
        while [ $# -gt 0 ]; do
          REST="${REST} \"\${$(($OPTIND-$#))}\""
          shift
        done
        break ;;
      [-]?*) set "unknown" "$1"; break ;;
      *)
        REST="${REST} \"\${$(($OPTIND-$#))}\""
    esac
    shift
  done
  [ $# -eq 0 ] && { OPTIND=1; unset OPTARG; return 0; }
  case $1 in
    unknown) set "Unrecognized option: $2" "$@" ;;
    noarg) set "Does not allow an argument: $2" "$@" ;;
    required) set "Requires an argument: $2" "$@" ;;
    pattern:*) set "Does not match the pattern (${1#*:}): $2" "$@" ;;
    notcmd) set "Not a command: $2" "$@" ;;
    *) set "Validation error ($1): $2" "$@"
  esac
  echo "$1" >&2
  exit 1
}
usage() {
cat<<'GETOPTIONSHERE'
Usage: example.sh [options]... [arguments]...
Options:
  -f, --flag                  takes no arguments
  -p, --param PARAM           takes one argument
  -o, --option[=OPTION]       takes one optional argument
  -h, --help
      --version
GETOPTIONSHERE
}

16

Ik wil mijn project indienen: https://github.com/flyingangel/argarer

source argparser.sh
parse_args "$@"

Eenvoudig als dat. De omgeving wordt gevuld met variabelen met dezelfde naam als de argumenten


17

Dit is hoe ik het in een functie doe om te voorkomen dat Getopts tegelijkertijd ergens hoger in stapel lopen:

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

18

Ik wil mijn versie van optie-parseren aanbieden, die het volgende mogelijk maakt:

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

Hiermee kunnen dit ook (kan ongewenst kunnen zijn):

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

U moet vóór gebruik beslissen of = voor een optie moet worden gebruikt of niet. Dit is om de code clean(ish) te houden.

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

Antwoord 19

Er zijn verschillende manieren om cmdline-args te ontleden (bijv. GNU getopt (niet draagbaar) versus BSD (MacOS) getopt versus getopts) – allemaal problematisch. Deze oplossing

  • is draagbaar!
  • heeft nul afhankelijkheden, vertrouwt alleen op bash ingebouwde-ins
  • maakt zowel korte als lange opties mogelijk
  • verwerkt witruimte of tegelijkertijd het gebruik van =scheidingsteken tussen optie en argument
  • ondersteunt aaneengeschakelde korte optiestijl -vxf
  • handvat optie met optionele argumenten (bijv. --colorvs --color=always),
  • detecteert en rapporteert onbekende opties correct
  • ondersteunt --om het einde van opties aan te geven, en
  • vereist geen opgeblazen code in vergelijking met alternatieven voor dezelfde functieset. D.w.z. beknopt en daardoor makkelijker te onderhouden

Voorbeelden: elk van

# flag
-f
--foo
# option with required argument
-b"Hello World"
-b "Hello World"
--bar "Hello World"
--bar="Hello World"
# option with optional argument
--baz
--baz="Optional Hello"

#!/usr/bin/env bash
usage() {
  cat - >&2 <<EOF
NAME
    program-name.sh - Brief description
SYNOPSIS
    program-name.sh [-h|--help]
    program-name.sh [-f|--foo]
                    [-b|--bar <arg>]
                    [--baz[=<arg>]]
                    [--]
                    FILE ...
REQUIRED ARGUMENTS
  FILE ...
          input files
OPTIONS
  -h, --help
          Prints this and exits
  -f, --foo
          A flag option
  -b, --bar <arg>
          Option requiring an argument <arg>
  --baz[=<arg>]
          Option that has an optional argument <arg>. If <arg>
          is not specified, defaults to 'DEFAULT'
  --     
          Specify end of options; useful if the first non option
          argument starts with a hyphen
EOF
}
fatal() {
    for i; do
        echo -e "${i}" >&2
    done
    exit 1
}
# For long option processing
next_arg() {
    if [[ $OPTARG == *=* ]]; then
        # for cases like '--opt=arg'
        OPTARG="${OPTARG#*=}"
    else
        # for cases like '--opt arg'
        OPTARG="${args[$OPTIND]}"
        OPTIND=$((OPTIND + 1))
    fi
}
# ':' means preceding option character expects one argument, except
# first ':' which make getopts run in silent mode. We handle errors with
# wildcard case catch. Long options are considered as the '-' character
optspec=":hfb:-:"
args=("" "$@")  # dummy first element so $1 and $args[1] are aligned
while getopts "$optspec" optchar; do
    case "$optchar" in
        h) usage; exit 0 ;;
        f) foo=1 ;;
        b) bar="$OPTARG" ;;
        -) # long option processing
            case "$OPTARG" in
                help)
                    usage; exit 0 ;;
                foo)
                    foo=1 ;;
                bar|bar=*) next_arg
                    bar="$OPTARG" ;;
                baz)
                    baz=DEFAULT ;;
                baz=*) next_arg
                    baz="$OPTARG" ;;
                -) break ;;
                *) fatal "Unknown option '--${OPTARG}'" "see '${0} --help' for usage" ;;
            esac
            ;;
        *) fatal "Unknown option: '-${OPTARG}'" "See '${0} --help' for usage" ;;
    esac
done
shift $((OPTIND-1))
if [ "$#" -eq 0 ]; then
    fatal "Expected at least one required argument FILE" \
    "See '${0} --help' for usage"
fi
echo "foo=$foo, bar=$bar, baz=$baz, files=${@}"

Antwoord 20

Oplossing die onverwerkte argumenten bewaart. Demo’s inbegrepen.

Hier is mijn oplossing. Het is ZEER flexibel en zou, in tegenstelling tot andere, geen externe pakketten moeten vereisen en overgebleven argumenten netjes afhandelen.

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

Het enige wat u hoeft te doen is de validflags-regel bewerken. Het voegt een koppelteken toe en doorzoekt alle argumenten. Het definieert dan het volgende argument als de vlagnaam, bijvoorbeeld

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

De hoofdcode (korte versie, uitgebreid met voorbeelden verder naar beneden, ook een versie met erroring out):

#!/usr/bin/env bash
#shebang.io
validflags="rate time number"
count=1
for arg in $@
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

De uitgebreide versie met ingebouwde ECHO-demo’s:

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
echo "all args
$@"
validflags="rate time number"
count=1
for arg in $@
do
    match=0
    argval=$1
#   argval=$(echo $@ | 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:
$@"
shift $#
echo "post final clear args:
$@"
set -- $leftovers
echo "all post set args:
$@"
echo arg1: $1 arg2: $2
echo leftovers: $leftovers
echo rate $rate time $time number $number

Finale, deze fouten eruit als een ongeldig -argument wordt doorgegeven.

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
validflags="rate time number"
count=1
for arg in $@
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

Pluspunten: wat het doet, werkt heel goed. Het behoudt ongebruikte argumenten die veel van de andere oplossingen hier niet doen. Het maakt het ook mogelijk om variabelen aan te roepen zonder handmatig in het script te worden gedefinieerd. Het staat ook pre-populatie van variabelen toe als er geen corresponderend argument wordt gegeven. (Zie uitgebreid voorbeeld).

Nadelen: kan geen enkele complexe arg-tekenreeks ontleden, b.v. -xcvf zou worden verwerkt als een enkel argument. Je zou enigszins gemakkelijk extra code in de mijne kunnen schrijven die deze functionaliteit toevoegt.


Antwoord 21

Merk op dat getopt(1)een kortstondige fout van AT&T was.

getopt is gemaakt in 1984 maar al begraven in 1986 omdat het niet echt bruikbaar was.

Een bewijs voor het feit dat getopterg verouderd is, is dat de getopt(1)man-pagina nog steeds "$*"vermeldt van "$@", dat in 1986 aan de Bourne Shell werd toegevoegd samen met de getopts(1)shell die ingebouwd was om argumenten met spaties binnenin om te gaan.

BTW: als je geïnteresseerd bent in het ontleden van lange opties in shellscripts, kan het interessant zijn om te weten dat de getopt(3)-implementatie van libc (Solaris) en ksh93beide hebben een uniforme implementatie van lange opties toegevoegd die lange opties ondersteunt als aliassen voor korte opties. Dit zorgt ervoor dat ksh93en de Bourne Shelleen uniforme interface voor lange opties implementeren via getopts.

Een voorbeeld voor lange opties uit de Bourne Shell man-pagina:

getopts "f:(file)(input-file)o:(output-file)" OPTX "$@"

laat zien hoe lang optie-aliassen mogen worden gebruikt in zowel Bourne Shell als ksh93.

Zie de man-pagina van een recente Bourne Shell:

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

en de man-pagina voor getopt(3) van OpenSolaris:

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

en als laatste, de getopt(1) man-pagina om de verouderde $*:

. te verifiëren

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


Antwoord 22

Ik werd geïnspireerd door het relatief eenvoudige antwoord van @bronsonen kwam in de verleiding om het te verbeteren (zonder teveel toe te voegen) complexiteit). Dit is het resultaat:

Nog een Shell Argument Parser (ASAP) – POSIX, geen getopt*

  • Gebruik een van de -n [arg], -abn [arg], --name [arg]en --name=argstijlen van opties;
  • Argumenten kunnen in elke volgorde voorkomen, alleen positieve argumenten blijven achter in$@na de lus;
  • Gebruik--om te forcerende resterende argumenten als positioneel te worden behandeld;
  • Detecteert ongeldige opties en ontbrekende argumenten;
  • Hangt niet af van getopt(s)of externe tools (een functie gebruikt een eenvoudig sed-commando);
  • Draagbaar, compact, goed leesbaar, met onafhankelijke functies.
# Convenience functions.
usage_error () { echo >&2 "$(basename $0):  $1"; exit 2; }
assert_argument () { test "$1" != "$EOL" || usage_error "$2 requires an argument"; }
# One loop, nothing more.
EOL=$(echo '\01\03\03\07')
if [ "$#" != 0 ]; then
  set -- "$@" "$EOL"
  while [ "$1" != "$EOL" ]; do
    opt="$1"; shift
    case "$opt" in
      # Your options go here.
      -f|--flag) flag=true;;
      -n|--name) assert_argument "$1" $opt; name="$1"; shift;;
      -|''|[^-]*) set -- "$@" "$opt";;                                          # positional argument, rotate to the end
      # Extra features (you may remove any line you don't need):
      --*=*)      set -- "${opt%%=*}" "${opt#*=}" "$@";;                        # convert '--name=arg' to '--name' 'arg'
      -[^-]?*)    set -- $(echo "${opt#- }" | sed 's/\(.\)/ -\1/g') "$@";;       # convert '-abc' to '-a' '-b' '-c'
      --)         while [ "$1" != "$EOL" ]; do set -- "$@" "$1"; shift; done;;  # process remaining arguments as positional
      -*)         usage_error "unknown option: '$opt'";;                        # catch misspelled options
      *)          usage_error "this should NEVER happen ($opt)";;               # sanity test for previous patterns
    esac
  done
  shift # $EOL
fi
# Do something cool with "$@"... \o/

Opmerking:Ik weet het… Een argument met het binaire patroon0x01030307zou de logica kunnen breken. Maar als iemand zo’n argument in een opdrachtregel gebruikt, verdienen ze het.


Antwoord 23

Positionele en op vlaggen gebaseerde argumenten mengen

–param=arg (is gelijk aan gescheiden)

Vlaggen vrijelijk mengen tussen positionele argumenten:

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

kan worden bereikt met een vrij beknopte aanpak:

# 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 (door spaties gescheiden)

Het is meestal duidelijker om de stijlen --flag=valueen --flag valueniet te mixen.

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

Dit is een beetje lastig om te lezen, maar is nog steeds geldig

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

Bron

# 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

Antwoord 24

Ik heb een bash-helper geschreven om een mooie bash-tool te schrijven

startpagina project: https://gitlab.mbedsys.org/mbedsys/bashopts

voorbeeld:

#!/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 "$@"
# Process argument
bashopts_process_args

geeft hulp:

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

Geniet 🙂


25

Hier is mijn aanpak – met behulp van Regexp.

  • Geen gettopts
  • Het behandelt blok van korte parameters -qwerty
  • Het behandelt korte parameters -q -w -e
  • Het behandelt lange opties --qwerty
  • U kunt het kenmerk van korte of lange of lange optie doorgeven (als u een blok korte opties gebruikt, is het kenmerk bij de laatste optie bevestigd)
  • U kunt spaties of =gebruiken om attributen te verschaffen, maar attribuutwedstrijden totdat u koppelt + spatie “scheidingsteken”, dus in --q=qwe tyqwe tyis een attribuut
  • het verwerkt een mix van al het bovenstaande, dus -o a -op attr ibute --option=att ribu te --op-tion attribute --option att-ributeis geldig

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
}
options=$@
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

Antwoord 26

Stel dat we een shellscript maken met de naam test_args.shals volgt

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

Nadat we de volgende opdracht hebben uitgevoerd:

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

De uitvoer zou zijn:

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

Antwoord 27

Ik wilde delen wat ik heb gemaakt voor het parseren van opties.
Sommige van mijn behoeften werden niet vervuld door de antwoorden hier, dus moest ik dit bedenken: https://github. com/MihirLuthra/bash_option_parser

Dit ondersteunt:

  • Ontleding van subopties
  • Aliasnamen voor opties
  • Optionele argumenten
  • Variabele argumenten
  • Afdrukgebruik en fouten

Stel dat we een commando hebben met de naam fruitmet het volgende gebruik:

fruit <fruit-name> ...
   [-e|—-eat|—-chew]
   [-c|--cut <how> <why>]
   <command> [<args>] 

-emaakt geen ruzie
-cheeft twee argumenten, namelijk hoe te knippen en waarom te knippen
fruitzelf heeft minstens één argument.
<command>is voor subopties zoals apple, orangeetc. (vergelijkbaar met gitdie subopties heeft commit, pushenz. )

Dus om het te ontleden:

parse_options \
    'fruit'                         '1 ...'  \
    '-e'     , '--eat' , '--chew'   '0'      \
    '-c'     , '--cut'              '1 1'    \
    'apple'                         'S'      \
    'orange'                        'S'      \
    ';' \
    "$@"

Als er een gebruiksfout is opgetreden, kan deze als volgt worden afgedrukt met option_parser_error_msg:

retval=$?
if [ $retval -ne 0 ]; then
    # this will manage error messages if
    # insufficient or extra args are supplied
    option_parser_error_msg "$retval"
    # This will print the usage
    print_usage 'fruit'
    exit 1
fi

Om nu te controleren of sommige opties zijn doorgegeven,

if [ -n "${OPTIONS[-c]}" ]
then
    echo "-c was passed"
    # args can be accessed in a 2D-array-like format
    echo "Arg1 to -c = ${ARGS[-c,0]}"
    echo "Arg2 to -c = ${ARGS[-c,1]}"
fi

Het parseren van subopties kan ook worden gedaan door $shift_countdoor te geven aan parse_options_detailed, waardoor het begint te parsen nadat de argumenten zijn verschoven om de argumenten van de suboptie te bereiken. Het wordt gedemonstreerd in dit voorbeeld.

Een gedetailleerde beschrijving vindt u in het leesmij-bestand en voorbeelden
in de repository.


Antwoord 28

Gebruik module “argumenten” van bash-modules

Voorbeeld:

#!/bin/bash
. import.sh log arguments
NAME="world"
parse_arguments "-n|--name)NAME;S" -- "$@" || {
  error "Cannot parse command line."
  exit 1
}
info "Hello, $NAME!"

Antwoord 29

Hier is een Getopts die het parseren met minimale code behaalt en kunt u definiëren wat u in één geval wilt extraheren met behulp van EVY met substring.

In principe eval "local key='val'"

function myrsync() {
        local backup=("${@}") args=(); while [[ $# -gt 0 ]]; do k="$1";
                case "$k" in
                    ---sourceuser|---sourceurl|---targetuser|---targeturl|---file|---exclude|---include)
                        eval "local ${k:3}='${2}'"; shift; shift    # Past two arguments
                    ;;
                    *)  # Unknown option  
                        args+=("$1"); shift;                        # Past argument only
                    ;;                                              
                esac                                                
        done; set -- "${backup[@]}"                                 # Restore $@
        echo "${sourceurl}"
}

Verkrijgt de variabelen als locals in plaats van globalen als de meeste antwoorden hier.

gebeld als:

myrsync ---sourceurl http://abc.def.g ---sourceuser myuser ... 

De $ {K: 3} is in feite een substring om de eerste ---van de sleutel te verwijderen.

Other episodes