web-dev-qa-db-fra.com

Comment analyser les arguments de ligne de commande dans Bash?

Dis, j'ai un script qui s'appelle avec cette ligne:

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

ou celui-ci:

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

Quel est le moyen accepté d’analyser cela de telle sorte que dans chaque cas (ou une combinaison des deux) $v, $f et $d seront tous définis à true et que $outFile sera égal à /fizz/someOtherFile?

1521
Lawrence Johnston

Méthode n ° 1: Utiliser bash sans getopt [s]

Les deux méthodes les plus courantes pour passer des arguments clé-paire-paire sont les suivantes:

Bash séparé par des espaces (par exemple, --option argument) (sans getopt [s])

Usage ./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 est égal à séparé (par exemple, --option=argument) (sans getopt [s])

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

#!/bin/bash

for i in "$@"
do
case $i in
    -e=*|--extension=*)
    EXTENSION="${i#*=}"
    shift # past argument=value
    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    shift # past argument=value
    ;;
    -l=*|--lib=*)
    LIBPATH="${i#*=}"
    shift # past argument=value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument with no value
    ;;
    *)
          # unknown option
    ;;
esac
done
echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 $1
fi

Pour mieux comprendre ${i#*=}, recherchez "Suppression de sous-chaîne" dans ce guide . Il est fonctionnellement équivalent à `sed 's/[^=]*=//' <<< "$i"` qui appelle un sous-processus inutile ou à `echo "$i" | sed 's/[^=]*=//'` qui appelle deux sous-processus inutiles. 

Méthode n ° 2: Utiliser bash avec getopt [s]

à partir de: http://mywiki.wooledge.org/BashFAQ/035#getopts

getopt (1) limitations (anciennes versions getopt relativement récentes): 

  • ne peut pas gérer les arguments qui sont des chaînes vides
  • ne peut pas gérer les arguments avec des espaces blancs incorporés

Les versions getopt plus récentes n'ont pas ces limitations.

De plus, le shell POSIX (et d’autres) offrent getopts qui n’a pas ces limitations. Voici un exemple simpliste getopts:

#!/bin/sh

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

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

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

shift $((OPTIND-1))

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

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

# End of file

Les avantages de getopts sont les suivants:

  1. Il est plus portable et fonctionnera dans d’autres coques comme dash
  2. Il peut gérer plusieurs options simples telles que -vf filename de la manière typique d'Unix, automatiquement.

L'inconvénient de getopts est qu'il ne peut gérer que des options courtes (-h, pas --help) sans code supplémentaire.

Il existe un getopts tutorial qui explique la signification de toutes les syntaxes et variables. Dans bash, il y a aussi help getopts, qui pourrait être informatif.

2173
Bruno Bronosky

Pas de réponse mentionne getopt amélioré. Et le réponse votée en haut est trompeur: Il ignore les options abrégées de style -⁠vfd (demandées par l'OP), les options après les arguments de position (également requis par l'OP) et ignore les erreurs d'analyse syntaxique. Au lieu:

  • Utilise getopt amélioré depuis util-linux ou anciennement GNU glibc.1
  • Cela fonctionne avec getopt_long() la fonction C de GNU glibc.
  • Possède tous caractéristiques distinctives utiles (les autres ne les ont pas):
    • gère les espaces, les guillemets et même les arguments binaires2
    • il peut gérer les options à la fin: script.sh -o outFile file1 file2 -v
    • permet =- style long options: script.sh --outfile=fileOut --infile fileIn
  • Est si vieux déjà3 qu’aucun système GNU ne manque (c’est le cas de tout Linux).
  • Vous pouvez tester son existence avec: getopt --test → renvoyer la valeur 4.
  • Les autres getopt ou getopts construit par Shell ont une utilité limitée.

Les appels suivants

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

tout retour

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

avec la suivante myscript

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

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

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

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

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

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

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

1 getopt amélioré est disponible sur la plupart des «systèmes bash», y compris Cygwin; sur OS X essayez brew installez gnu-getopt ou Sudo port install getopt
2 Les conventions POSIX exec() ne disposent d'aucun moyen fiable pour passer la valeur NULL binaire dans les arguments de ligne de commande; ces octets mettent fin prématurément à l'argument
3 première version parue en 1997 ou avant (je ne l'ai retracée qu'en 1997)

419
Robert Siemer

à partir de: digitalpeer.com avec des modifications mineures

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

Pour mieux comprendre ${i#*=}, recherchez "Suppression de sous-chaîne" dans ce guide . Il est fonctionnellement équivalent à `sed 's/[^=]*=//' <<< "$i"` qui appelle un sous-processus inutile ou à `echo "$i" | sed 's/[^=]*=//'` qui appelle deux sous-processus inutiles.

114
guneysus

getopt()/getopts() est une bonne option. Volé de ici :

L'utilisation simple de "getopt" est montrée dans ce 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

Ce que nous avons dit, c’est que l’un quelconque des -a, -b, -c ou -d seront autorisés, mais -c est suivi d'un argument (le "c:" le dit).

Si nous appelons cela "g" et l'essayons:

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

Nous commençons avec deux arguments, et "getopt" divise les options et met chacun dans son propre argument. Ça aussi ajoutée "--".

102
Matt J

Au risque d'ajouter un autre exemple à ignorer, voici mon schéma.

  • gère -n arg et --name=arg
  • permet des arguments à la fin
  • affiche des erreurs saines si quelque chose est mal orthographié
  • compatible, n'utilise pas de bashismes
  • lisible, ne nécessite pas de maintenir l'état dans une boucle

J'espère que c'est utile pour quelqu'un.

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

Manière plus succincte

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"

Utilisation:

./script.sh -d dev -u

# OR:

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

Je suis environ 4 ans en retard à cette question, mais je veux donner en retour. J’ai utilisé les réponses précédentes comme point de départ pour ranger mon ancien paramétrage ad hoc. J'ai ensuite refactored le code de modèle suivant. Il gère les paramètres long et court, en utilisant des arguments = ou séparés par des espaces, ainsi que plusieurs paramètres courts regroupés. Enfin, il réinsère tous les arguments non-param dans les variables $ 1, $ 2 ... J'espère que c'est utile.

#!/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
39
Shane Day

Ma réponse est largement basée sur la réponse de Bruno Bronosky , mais j'ai en quelque sorte écrasé ses deux implémentations de bash pur dans une application que j'utilise assez fréquemment.

# 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

Cela vous permet d'avoir à la fois des options/valeurs séparées par un espace, ainsi que des valeurs définies égales.

Vous pouvez donc exécuter votre script en utilisant:

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

aussi bien que:

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

et les deux devraient avoir le même résultat final.

AVANTAGES:

  • Permet à la fois les valeurs -arg = et -arg

  • Fonctionne avec n'importe quel nom d'argument que vous pouvez utiliser dans bash

    • Signification -a ou -arg ou --arg ou -a-r-g ou autre chose
  • Pure bash. Pas besoin d'apprendre/d'utiliser getopt ou getopts

LES INCONVÉNIENTS:

  • Impossible de combiner les arguments

    • Ce qui signifie non -abc. Vous devez faire -a -b -c

Ce sont les seuls avantages/inconvénients que je peux penser à la tête de ma tête

25
Ponyboy47

J'ai trouvé le problème d'écrire des analyses syntaxiques portables dans des scripts si frustrant que j'ai écrit Argbash - un générateur de code FOSS capable de générer le code d'analyse d'arguments pour votre script, ainsi que certaines fonctionnalités intéressantes:

https://argbash.io

24
bubla

Développant l'excellente réponse de @guneysus, voici un Tweak qui permet à l'utilisateur d'utiliser la syntaxe de son choix, par exemple:

command -x=myfilename.ext --another_switch 

contre

command -x myfilename.ext --another_switch

C'est-à-dire que les égaux peuvent être remplacés par des espaces. 

Cette "interprétation floue" ne vous conviendra peut-être pas, mais si vous créez des scripts interchangeables avec d’autres utilitaires (comme c’est le cas du mien, qui doit fonctionner avec ffmpeg), la flexibilité est utile.

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
13
unsynchronized

Je pense que celui-ci est assez simple à utiliser:

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

Exemple d'invocation:

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

Je vous donne la fonction parse_params qui analysera les paramètres à partir de la ligne de commande.

  1. C'est une solution pure Bash, sans utilitaire supplémentaire.
  2. Ne pollue pas la portée mondiale. 
  3. Vous ramène sans effort des variables simples à utiliser, sur lesquelles vous pourriez construire une logique plus poussée.
  4. La quantité de tirets avant que les paramètres ne soient pas importants (--all est égal à -all est égal à all=all)

Le script ci-dessous est une démonstration de travail copier-coller. Voir fonction show_use pour comprendre comment utiliser parse_params.

Limites: 

  1. Ne supporte pas les paramètres délimités par de l'espace (-d 1)
  2. Les noms de paramètres perdront des tirets, donc --any-param et -anyparam sont équivalents
  3. eval $(parse_params "$@") doit être utilisé dans bash function (cela ne fonctionnera pas dans la portée globale)

#!/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 "$@")
    # --
    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
Oleksii Chekulaiev

getopts fonctionne très bien si # 1 vous l'avez installé et # 2 vous avez l'intention de l'exécuter sur la même plate-forme. OSX et Linux (par exemple) se comportent différemment à cet égard.

Voici une solution (non getopts) qui prend en charge les drapeaux égaux, non égaux et booléens. Par exemple, vous pouvez exécuter votre script de cette manière:

./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
8
vangorra

EasyOptions ne nécessite aucune 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

Voici comment je fais dans une fonction pour éviter de casser des getopts exécutés en même temps quelque part plus haut dans la pile:

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

J'aimerais proposer ma version d'analyse des options, qui permet:

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

Permet également ceci (pourrait être indésirable):

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

Vous devez décider avant d'utiliser si = doit être utilisé pour une option ou non. C'est pour garder le code propre (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

Supposons que nous créons un script Shell nommé test_args.sh comme suit

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

Après avoir exécuté la commande suivante:

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

La sortie serait:

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

Mélange d'arguments positionnels et basés sur des indicateurs

--param = arg (égal à égal)

Mélanger librement les drapeaux entre les arguments de position:

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

peut être accompli avec une approche assez concise:

# 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 (délimité par des espaces)

Il est généralement plus clair de ne pas mélanger les styles --flag=value et --flag value.

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

C'est un peu risqué à lire, mais est toujours valide

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

La source

# 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

J'ai écrit un assistant bash pour écrire un outil Nice bash

projet accueil: https://gitlab.mbedsys.org/mbedsys/bashopts

exemple:

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

va donner de l'aide:

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

prendre plaisir :)

2
Emeric Verschuur

Voici mon approche - en utilisant regexp.

  • pas de getopts
  • il gère un bloc de paramètres courts -qwerty
  • il gère les paramètres courts -q -w -e
  • il gère les options longues --qwerty
  • vous pouvez passer attribut à l'option courte ou longue (si vous utilisez un bloc d'options courtes, l'attribut est attaché à la dernière option)
  • vous pouvez utiliser des espaces ou = pour fournir des attributs, mais les attributs correspondent jusqu'à ce que le tiret + espace "délimiteur" se trouve, ainsi dans --q=qwe tyqwe ty un attribut
  • il gère le mélange de tous les éléments ci-dessus, donc -o a -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute est valide

scénario:

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

Solution qui préserve les arguments non gérés. Démos incluses.

Voici ma solution. Il est TRÈS flexible et contrairement aux autres, il ne devrait pas nécessiter de paquet externe et gère les arguments restants de manière propre.

L'utilisation est: ./myscript -flag flagvariable -otherflag flagvar2

Tout ce que vous avez à faire est d’éditer la ligne validflags. Il ajoute un trait d'union et recherche tous les arguments. Il définit ensuite l’argument suivant comme nom de drapeau, par exemple.

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

Le code principal (version courte, détaillé avec des exemples plus bas, également une version avec erreur):

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

La version détaillée avec des démos d'écho intégré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

Final one, celui-ci est erroné si un argument invalide est passé.

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

Avantages: Ce que ça fait, ça se gère très bien. Cela préserve les arguments inutilisés, ce que beaucoup d'autres solutions ne font pas ici. Cela permet également aux variables d'être appelées sans être définies manuellement dans le script. Cela permet également de pré-remplir des variables si aucun argument correspondant n’est donné. (Voir exemple détaillé).

Inconvénients: Impossible d'analyser une seule chaîne d'argument complexe, par exemple. -xcvf serait traité comme un seul argument. Vous pourriez assez facilement écrire du code supplémentaire dans le mien qui ajoute cette fonctionnalité. 

2
Noah

Une autre solution sans getopt [s], POSIX, ancien style Unix

Semblable à la solution proposée par Bruno Bronosky celle-ci en est une sans l'utilisation de getopt(s).

La principale caractéristique de ma solution est qu’elle permet de concaténer des options, tout comme tar -xzf foo.tar.gz est égal à tar -x -z -f foo.tar.gz. Et comme dans tar, ps etc., le trait d'union est facultatif pour un bloc d'options abrégées (mais cela peut être modifié facilement). Les options longues sont également prises en charge (mais lorsqu'un bloc commence par un, deux traits d'union s'imposent).

Code avec des exemples d'options

#!/bin/sh

echo
echo "POSIX-compliant getopt(s)-free old-style-supporting option parser from phk@[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

Pour un exemple d'utilisation, veuillez vous reporter aux exemples ci-dessous.

Position des options avec arguments

Pour ce qui en vaut la peine, les options avec arguments ne sont pas les dernières (seules les options longues doivent être). Donc, par exemple Dans tar (du moins dans certaines implémentations), les options f doivent être dernière, car le nom du fichier suit (tar xzf bar.tar.gz fonctionne mais pas tar xfz bar.tar.gz). Ce n'est pas le cas ici (voir les exemples suivants).

Plusieurs options avec des arguments

En prime, les paramètres d’option sont utilisés dans l’ordre des options par les paramètres avec les options requises. Il suffit de regarder la sortie de mon script ici avec la ligne de commande abc X Y Z (ou -abc X Y Z):

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

De longues options concaténées aussi

En outre, vous pouvez également avoir de longues options dans le bloc d'options, étant donné qu'elles se trouvent en dernier dans le bloc. Les lignes de commande suivantes sont donc toutes équivalentes (y compris l'ordre dans lequel les options et leurs arguments sont en cours de traitement):

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

Tout cela mène à:

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

Pas dans cette solution

Arguments optionnels

Les options avec des arguments optionnels devraient être possibles avec un peu de travail, par exemple. en regardant s'il y a un bloc sans trait d'union; l'utilisateur devrait alors mettre un trait d'union devant chaque bloc suivant un bloc avec un paramètre ayant un paramètre facultatif. C’est peut-être trop compliqué à communiquer à l’utilisateur, il est donc préférable d’exiger un trait d’union dans ce cas.

Les choses deviennent encore plus compliquées avec plusieurs paramètres possibles. Je vous déconseille de choisir les options en essayant d’être intelligentes en déterminant si un argument peut être pour ou non (par exemple, une option prend simplement un nombre en tant qu’argument optionnel) car cela pourrait ne plus être envisageable à l’avenir.

Personnellement, je privilégie les options supplémentaires au lieu des arguments optionnels.

Arguments d'option introduits avec un signe égal

Tout comme pour les arguments optionnels, je ne suis pas un fan de cela (BTW, existe-t-il un fil pour discuter des avantages/inconvénients de différents styles de paramètres?) Mais si vous le souhaitez, vous pouvez probablement le mettre en œuvre vous-même, comme vous le faites sur http: //mywiki.wooledge.org/BashFAQ/035#Manual_loop avec une instruction --long-with-arg=?* case puis en supprimant le signe égal (c'est BTW le site qui dit que faire la concaténation de paramètres est possible avec un certain effort mais "laissé [it] comme un exercice pour le lecteur "qui m’a poussé à les prendre à leur mot mais je suis parti de zéro).

Autres notes

Compatible POSIX, fonctionne même sur les anciennes configurations Busybox auxquelles j'ai dû faire face (par exemple, cut, head et getopts sont manquants).

1
phk

Cela peut aussi être utile de savoir, vous pouvez définir une valeur et si quelqu'un fournit une entrée, remplacez la valeur par défaut par cette valeur. 

myscript.sh -f ./serverlist.txt ou simplement ./myscript.sh (et il prend les valeurs par défaut)

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

Voici ma solution améliorée de la réponse de Bruno Bronosky en utilisant des tableaux variables.

il vous permet de mélanger les paramètres de position et de vous donner un tableau de paramètres préservant l'ordre sans les options

#!/bin/bash

echo $@

PARAMS=()
SOFT=0
SKIP=()
for i in "$@"
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[@]}

Produira par exemple:

$ ./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

En développant la réponse de @ bruno-bronosky, j'ai ajouté un "pré-processeur" pour gérer un formatage courant:

  • Développe --longopt=val dans --longopt val
  • Développe -xyz dans -x -y -z
  • Prend en charge -- pour indiquer la fin des drapeaux
  • Affiche une erreur pour les options inattendues
  • Commutateur d'options compacts et faciles à lire
#!/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

Cet exemple montre comment utiliser getopt et eval et HEREDOC et shift pour gérer des paramètres courts et longs avec et sans la valeur requise qui suit. En outre, la déclaration de commutateur/affaire est concise et facile à suivre.

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

Les lignes les plus significatives du script ci-dessus sont les suivantes:

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

Court, droit au but, lisible, et gère à peu près tout (IMHO).

J'espère que ça aide quelqu'un.

1
phyatt

Utiliser le module "arguments" de bash-modules

Exemple:

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

NAME="world"

parse_arguments "-n|--name)NAME;S" -- "$@" || {
  error "Cannot parse command line."
  exit 1
}

info "Hello, $NAME!"
1

Je veux soumettre mon projet: https://github.com/flyingangel/argparser

source argparser.sh
parse_args "$@"

Aussi simple que cela. L'environnement sera peuplé de variables portant le même nom que les arguments

1
Thanh Trung

La meilleure réponse à cette question a semblé un peu dérangeante lorsque je l'ai essayée - voici la solution que j'ai trouvée plus robuste:

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

Simple et facile à modifier, les paramètres peuvent être dans n'importe quel ordre. cela peut être modifié pour prendre des paramètres sous n'importe quelle forme (-a, --a, a, etc.).

for arg in "$@"
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