Comment obtenir des arguments avec des drapeaux dans Bash

284

Je sais que je peux facilement obtenir des paramètres positionnés comme celui-ci dans bash:

$0 ou $1

Je veux pouvoir utiliser des options de drapeau comme celle-ci pour spécifier à quoi chaque paramètre est utilisé:

mysql -u user -h host

Quelle est la meilleure façon d'obtenir de la -u paramvaleur et de la -h paramvaleur par indicateur plutôt que par position?

Stann
la source
2
Ce pourrait être une bonne idée de demander / vérifier sur unix.stackexchange.com également
MRR0GERS
8
google pour "bash getopts" - beaucoup de tutoriels.
glenn jackman
89
@ glenn-jackman: Je vais définitivement le chercher sur Google maintenant que je connais le nom. La chose à propos de Google est - pour poser une question - vous devriez déjà connaître 50% de la réponse.
Stann

Réponses:

292

C'est l'idiome que j'utilise habituellement:

while test $# -gt 0; do
  case "$1" in
    -h|--help)
      echo "$package - attempt to capture frames"
      echo " "
      echo "$package [options] application [arguments]"
      echo " "
      echo "options:"
      echo "-h, --help                show brief help"
      echo "-a, --action=ACTION       specify an action to use"
      echo "-o, --output-dir=DIR      specify a directory to store output in"
      exit 0
      ;;
    -a)
      shift
      if test $# -gt 0; then
        export PROCESS=$1
      else
        echo "no process specified"
        exit 1
      fi
      shift
      ;;
    --action*)
      export PROCESS=`echo $1 | sed -e 's/^[^=]*=//g'`
      shift
      ;;
    -o)
      shift
      if test $# -gt 0; then
        export OUTPUT=$1
      else
        echo "no output dir specified"
        exit 1
      fi
      shift
      ;;
    --output-dir*)
      export OUTPUT=`echo $1 | sed -e 's/^[^=]*=//g'`
      shift
      ;;
    *)
      break
      ;;
  esac
done

Les points clés sont:

  • $# est le nombre d'arguments
  • tandis que la boucle examine tous les arguments fournis, correspondant à leurs valeurs dans une instruction case
  • shift enlève le premier. Vous pouvez déplacer plusieurs fois à l'intérieur d'une instruction case pour prendre plusieurs valeurs.
Flexo
la source
3
Que font les cas --action*et --output-dir*?
Lucio
1
Ils sauvegardent simplement les valeurs qu'ils obtiennent dans l'environnement.
Flexo
22
@Lucio Super ancien commentaire, mais en l'ajoutant au cas où quelqu'un d'autre visiterait cette page. Le * (caractère générique) est pour le cas où quelqu'un tape --action=[ACTION]ainsi que le cas où quelqu'un tape--action [ACTION]
cooper
2
Pourquoi pour *)vous cassez-vous là-bas, ne devriez-vous pas quitter ou ignorer la mauvaise option? En d'autres termes, -bad -o dirla -o dirpièce n'est jamais traitée.
newguy
@newguy bonne question. Je pense que j'essayais de les laisser passer à autre chose
Flexo
428

Cet exemple utilise la getoptscommande intégrée de Bash et provient du guide de style Google Shell :

a_flag=''
b_flag=''
files=''
verbose='false'

print_usage() {
  printf "Usage: ..."
}

while getopts 'abf:v' flag; do
  case "${flag}" in
    a) a_flag='true' ;;
    b) b_flag='true' ;;
    f) files="${OPTARG}" ;;
    v) verbose='true' ;;
    *) print_usage
       exit 1 ;;
  esac
done

Remarque: Si un caractère est suivi de deux points (par exemple f:), cette option devrait avoir un argument.

Exemple d'utilisation: ./script -v -a -b -f filename

L'utilisation de getopts présente plusieurs avantages par rapport à la réponse acceptée:

  • la condition while est beaucoup plus lisible et montre quelles sont les options acceptées
  • code plus propre; sans compter le nombre de paramètres et le décalage
  • vous pouvez rejoindre des options (par exemple -a -b -c-abc)

Cependant, un gros inconvénient est qu'il ne prend pas en charge les options longues, uniquement les options à un seul caractère.

Dennis
la source
48
On se demande pourquoi cette réponse, en utilisant un bash builtin, n'est pas la meilleure
Will Barnwell
13
Pour la postérité: les deux points après dans 'abf: v' indiquent que -f prend un argument supplémentaire (le nom de fichier dans ce cas).
zahbaz
1
J'ai dû changer la ligne d'erreur en ceci:?) printf '\nUsage: %s: [-a] aflag [-b] bflag\n' $0; exit 2 ;;
Andy
7
Pourriez-vous ajouter une note sur les deux points? En ce qu'après chaque lettre, aucun deux-points ne signifie aucun argument, un deux-points signifie un argument et deux deux points signifie un argument facultatif?
limasxgoesto0
3
@WillBarnwell, il faut noter qu'elle a été ajoutée 3 ans après la question, alors que la première réponse a été ajoutée le même jour.
rbennell
47

getopt est votre ami .. un exemple simple:

function f () {
TEMP=`getopt --long -o "u:h:" "$@"`
eval set -- "$TEMP"
while true ; do
    case "$1" in
        -u )
            user=$2
            shift 2
        ;;
        -h )
            host=$2
            shift 2
        ;;
        *)
            break
        ;;
    esac 
done;

echo "user = $user, host = $host"
}

f -u myself -h some_host

Il devrait y avoir divers exemples dans votre répertoire / usr / bin.

Shizzmo
la source
3
Un exemple plus complet peut être trouvé dans le répertoire /usr/share/doc/util-linux/examples, au moins sur les machines Ubuntu.
Serge Stroobandt du
10

Je pense que cela servirait d'exemple plus simple de ce que vous voulez réaliser. Il n'est pas nécessaire d'utiliser des outils externes. Les outils Bash intégrés peuvent faire le travail pour vous.

function DOSOMETHING {

   while test $# -gt 0; do
           case "$1" in
                -first)
                    shift
                    first_argument=$1
                    shift
                    ;;
                -last)
                    shift
                    last_argument=$1
                    shift
                    ;;
                *)
                   echo "$1 is not a recognized flag!"
                   return 1;
                   ;;
          esac
  done  

  echo "First argument : $first_argument";
  echo "Last argument : $last_argument";
 }

Cela vous permettra d'utiliser des indicateurs, donc peu importe l'ordre dans lequel vous passez les paramètres, vous obtiendrez le bon comportement.

Exemple :

 DOSOMETHING -last "Adios" -first "Hola"

Production :

 First argument : Hola
 Last argument : Adios

Vous pouvez ajouter cette fonction à votre profil ou la placer dans un script.

Merci!

Modifier: enregistrez-le sous forme de fichier puis exécutez-le sous yourfile.sh -last "Adios" -first "Hola"

#!/bin/bash
while test $# -gt 0; do
           case "$1" in
                -first)
                    shift
                    first_argument=$1
                    shift
                    ;;
                -last)
                    shift
                    last_argument=$1
                    shift
                    ;;
                *)
                   echo "$1 is not a recognized flag!"
                   return 1;
                   ;;
          esac
  done  

  echo "First argument : $first_argument";
  echo "Last argument : $last_argument";
Matias Barrios
la source
J'utilise le code ci-dessus et lorsqu'il est exécuté, il n'imprime rien. ./hello.sh DOSOMETHING -last "Adios" -first "Hola"
dinu0101
@ dinu0101 Ceci est une fonction. Pas un script. Vous devez l'utiliser comme DOSOMETHING -dernier "Adios" -premier "Hola"
Matias Barrios
Merci @Matias. Compris. comment exécuter à l'intérieur du script.
dinu0101
1
Merci beaucoup @Matias
dinu0101
2
Utilisation return 1;avec le dernier exemple de sorties can only 'return' from a function or sourced scriptsur macOS. Le passage à exit 1;fonctionne comme prévu cependant.
Mattias
5

Une autre alternative serait d'utiliser quelque chose comme l'exemple ci-dessous qui vous permettrait d'utiliser des balises longues --image ou courtes -i et également autoriser les méthodes compilées -i = "example.jpg" ou -i example.jpg séparées pour passer des arguments .

# declaring a couple of associative arrays
declare -A arguments=();  
declare -A variables=();

# declaring an index integer
declare -i index=1;

# any variables you want to use here
# on the left left side is argument label or key (entered at the command line along with it's value) 
# on the right side is the variable name the value of these arguments should be mapped to.
# (the examples above show how these are being passed into this script)
variables["-gu"]="git_user";  
variables["--git-user"]="git_user";  
variables["-gb"]="git_branch";  
variables["--git-branch"]="git_branch";  
variables["-dbr"]="db_fqdn";  
variables["--db-redirect"]="db_fqdn";  
variables["-e"]="environment";  
variables["--environment"]="environment";

# $@ here represents all arguments passed in
for i in "$@"  
do  
  arguments[$index]=$i;
  prev_index="$(expr $index - 1)";

  # this if block does something akin to "where $i contains ="
  # "%=*" here strips out everything from the = to the end of the argument leaving only the label
  if [[ $i == *"="* ]]
    then argument_label=${i%=*} 
    else argument_label=${arguments[$prev_index]}
  fi

  # this if block only evaluates to true if the argument label exists in the variables array
  if [[ -n ${variables[$argument_label]} ]]
    then
        # dynamically creating variables names using declare
        # "#$argument_label=" here strips out the label leaving only the value
        if [[ $i == *"="* ]]
            then declare ${variables[$argument_label]}=${i#$argument_label=} 
            else declare ${variables[$argument_label]}=${arguments[$index]}
        fi
  fi

  index=index+1;
done;

# then you could simply use the variables like so:
echo "$git_user";
Robert McMahan
la source
3

J'aime la réponse de Robert McMahan la meilleure ici car elle semble la plus facile à transformer en fichiers d'inclusion partageables pour n'importe lequel de vos scripts à utiliser. Mais il semble y avoir un défaut avec la ligne qui if [[ -n ${variables[$argument_label]} ]]lance le message, "variables: indice de mauvais tableau". Je n'ai pas le représentant pour commenter, et je doute que ce soit le bon «correctif», mais envelopper cela ifpour le if [[ -n $argument_label ]] ; thennettoyer.

Voici le code avec lequel je me suis retrouvé, si vous connaissez une meilleure façon, veuillez ajouter un commentaire à la réponse de Robert.

Inclure le fichier "flags-declares.sh"

# declaring a couple of associative arrays
declare -A arguments=();
declare -A variables=();

# declaring an index integer
declare -i index=1;

Inclure le fichier "flags-arguments.sh"

# $@ here represents all arguments passed in
for i in "$@"
do
  arguments[$index]=$i;
  prev_index="$(expr $index - 1)";

  # this if block does something akin to "where $i contains ="
  # "%=*" here strips out everything from the = to the end of the argument leaving only the label
  if [[ $i == *"="* ]]
    then argument_label=${i%=*}
    else argument_label=${arguments[$prev_index]}
  fi

  if [[ -n $argument_label ]] ; then
    # this if block only evaluates to true if the argument label exists in the variables array
    if [[ -n ${variables[$argument_label]} ]] ; then
      # dynamically creating variables names using declare
      # "#$argument_label=" here strips out the label leaving only the value
      if [[ $i == *"="* ]]
        then declare ${variables[$argument_label]}=${i#$argument_label=} 
        else declare ${variables[$argument_label]}=${arguments[$index]}
      fi
    fi
  fi

  index=index+1;
done;

Votre "script.sh"

. bin/includes/flags-declares.sh

# any variables you want to use here
# on the left left side is argument label or key (entered at the command line along with it's value) 
# on the right side is the variable name the value of these arguments should be mapped to.
# (the examples above show how these are being passed into this script)
variables["-gu"]="git_user";
variables["--git-user"]="git_user";
variables["-gb"]="git_branch";
variables["--git-branch"]="git_branch";
variables["-dbr"]="db_fqdn";
variables["--db-redirect"]="db_fqdn";
variables["-e"]="environment";
variables["--environment"]="environment";

. bin/includes/flags-arguments.sh

# then you could simply use the variables like so:
echo "$git_user";
echo "$git_branch";
echo "$db_fqdn";
echo "$environment";
Michael
la source
3

Si vous êtes familier avec Python argparse, et cela ne vous dérange pas d'appeler python pour analyser les arguments bash, il y a un morceau de code que j'ai trouvé très utile et super facile à utiliser appelé argparse-bash https://github.com/nhoffman/ argparse-bash

Exemple tiré de leur script example.sh:

#!/bin/bash

source $(dirname $0)/argparse.bash || exit 1
argparse "$@" <<EOF || exit 1
parser.add_argument('infile')
parser.add_argument('outfile')
parser.add_argument('-a', '--the-answer', default=42, type=int,
                    help='Pick a number [default %(default)s]')
parser.add_argument('-d', '--do-the-thing', action='store_true',
                    default=False, help='store a boolean [default %(default)s]')
parser.add_argument('-m', '--multiple', nargs='+',
                    help='multiple values allowed')
EOF

echo required infile: "$INFILE"
echo required outfile: "$OUTFILE"
echo the answer: "$THE_ANSWER"
echo -n do the thing?
if [[ $DO_THE_THING ]]; then
    echo " yes, do it"
else
    echo " no, do not do it"
fi
echo -n "arg with multiple values: "
for a in "${MULTIPLE[@]}"; do
    echo -n "[$a] "
done
echo
Linh
la source
2

Je propose un TLDR simple :; exemple pour les non-initiés.

Créez un script bash appelé helloworld.sh

#!/bin/bash

while getopts "n:" arg; do
  case $arg in
    n) Name=$OPTARG;;
  esac
done

echo "Hello $Name!"

Vous pouvez ensuite passer un paramètre facultatif -nlors de l'exécution du script.

Exécutez le script en tant que tel:

$ bash helloworld.sh -n 'World'

Production

$ Hello World!

Remarques

Si vous souhaitez utiliser plusieurs paramètres:

  1. étendre while getops "n:" arg: doavec plus de paramètres tels que while getops "n:o:p:" arg: do
  2. étendre le commutateur de cas avec des affectations de variables supplémentaires. Tels que o) Option=$OPTARGetp) Parameter=$OPTARG
pijemcolu
la source
1
#!/bin/bash

if getopts "n:" arg; then
  echo "Welcome $OPTARG"
fi

Enregistrez-le sous sample.sh et essayez d'exécuter

sh sample.sh -n John

dans votre terminal.

Nishant Ingle
la source
1

J'ai eu du mal à utiliser getopts avec plusieurs drapeaux, j'ai donc écrit ce code. Il utilise une variable modale pour détecter les indicateurs et utiliser ces indicateurs pour affecter des arguments aux variables.

Notez que si un indicateur ne doit pas avoir d'argument, autre chose que la définition de CURRENTFLAG peut être fait.

    for MYFIELD in "$@"; do

        CHECKFIRST=`echo $MYFIELD | cut -c1`

        if [ "$CHECKFIRST" == "-" ]; then
            mode="flag"
        else
            mode="arg"
        fi

        if [ "$mode" == "flag" ]; then
            case $MYFIELD in
                -a)
                    CURRENTFLAG="VARIABLE_A"
                    ;;
                -b)
                    CURRENTFLAG="VARIABLE_B"
                    ;;
                -c)
                    CURRENTFLAG="VARIABLE_C"
                    ;;
            esac
        elif [ "$mode" == "arg" ]; then
            case $CURRENTFLAG in
                VARIABLE_A)
                    VARIABLE_A="$MYFIELD"
                    ;;
                VARIABLE_B)
                    VARIABLE_B="$MYFIELD"
                    ;;
                VARIABLE_C)
                    VARIABLE_C="$MYFIELD"
                    ;;
            esac
        fi
    done
Jessica Richards
la source
0

Voici donc ma solution. Je voulais pouvoir gérer les drapeaux booléens sans trait d'union, avec un trait d'union, et avec deux tirets ainsi que l'affectation des paramètres / valeurs avec un et deux tirets.

# Handle multiple types of arguments and prints some variables
#
# Boolean flags
# 1) No hyphen
#    create   Assigns `true` to the variable `CREATE`.
#             Default is `CREATE_DEFAULT`.
#    delete   Assigns true to the variable `DELETE`.
#             Default is `DELETE_DEFAULT`.
# 2) One hyphen
#      a      Assigns `true` to a. Default is `false`.
#      b      Assigns `true` to b. Default is `false`.
# 3) Two hyphens
#    cats     Assigns `true` to `cats`. By default is not set.
#    dogs     Assigns `true` to `cats`. By default is not set.
#
# Parameter - Value
# 1) One hyphen
#      c      Assign any value you want
#      d      Assign any value you want
#
# 2) Two hyphens
#   ... Anything really, whatever two-hyphen argument is given that is not
#       defined as flag, will be defined with the next argument after it.
#
# Example:
# ./parser_example.sh delete -a -c VA_1 --cats --dir /path/to/dir
parser() {
    # Define arguments with one hyphen that are boolean flags
    HYPHEN_FLAGS="a b"
    # Define arguments with two hyphens that are boolean flags
    DHYPHEN_FLAGS="cats dogs"

    # Iterate over all the arguments
    while [ $# -gt 0 ]; do
        # Handle the arguments with no hyphen
        if [[ $1 != "-"* ]]; then
            echo "Argument with no hyphen!"
            echo $1
            # Assign true to argument $1
            declare $1=true
            # Shift arguments by one to the left
            shift
        # Handle the arguments with one hyphen
        elif [[ $1 == "-"[A-Za-z0-9]* ]]; then
            # Handle the flags
            if [[ $HYPHEN_FLAGS == *"${1/-/}"* ]]; then
                echo "Argument with one hyphen flag!"
                echo $1
                # Remove the hyphen from $1
                local param="${1/-/}"
                # Assign true to $param
                declare $param=true
                # Shift by one
                shift
            # Handle the parameter-value cases
            else
                echo "Argument with one hyphen value!"
                echo $1 $2
                # Remove the hyphen from $1
                local param="${1/-/}"
                # Assign argument $2 to $param
                declare $param="$2"
                # Shift by two
                shift 2
            fi
        # Handle the arguments with two hyphens
        elif [[ $1 == "--"[A-Za-z0-9]* ]]; then
            # NOTE: For double hyphen I am using `declare -g $param`.
            #   This is the case because I am assuming that's going to be
            #   the final name of the variable
            echo "Argument with two hypens!"
            # Handle the flags
            if [[ $DHYPHEN_FLAGS == *"${1/--/}"* ]]; then
                echo $1 true
                # Remove the hyphens from $1
                local param="${1/--/}"
                # Assign argument $2 to $param
                declare -g $param=true
                # Shift by two
                shift
            # Handle the parameter-value cases
            else
                echo $1 $2
                # Remove the hyphens from $1
                local param="${1/--/}"
                # Assign argument $2 to $param
                declare -g $param="$2"
                # Shift by two
                shift 2
            fi
        fi

    done
    # Default value for arguments with no hypheb
    CREATE=${create:-'CREATE_DEFAULT'}
    DELETE=${delete:-'DELETE_DEFAULT'}
    # Default value for arguments with one hypen flag
    VAR1=${a:-false}
    VAR2=${b:-false}
    # Default value for arguments with value
    # NOTE1: This is just for illustration in one line. We can well create
    #   another function to handle this. Here I am handling the cases where
    #   we have a full named argument and a contraction of it.
    #   For example `--arg1` can be also set with `-c`.
    # NOTE2: What we are doing here is to check if $arg is defined. If not,
    #   check if $c was defined. If not, assign the default value "VD_"
    VAR3=$(if [[ $arg1 ]]; then echo $arg1; else echo ${c:-"VD_1"}; fi)
    VAR4=$(if [[ $arg2 ]]; then echo $arg2; else echo ${d:-"VD_2"}; fi)
}


# Pass all the arguments given to the script to the parser function
parser "$@"


echo $CREATE $DELETE $VAR1 $VAR2 $VAR3 $VAR4 $cats $dir

Quelques références

  • La procédure principale a été trouvée ici .
  • Plus d'informations sur le passage de tous les arguments à une fonction ici .
  • Plus d'informations sur les valeurs par défaut ici .
  • Plus d'infos sur declaredo $ bash -c "help declare".
  • Plus d'infos sur shiftdo $ bash -c "help shift".
H. Sánchez
la source