web-dev-qa-db-fra.com

Comment obtenir des arguments avec des drapeaux dans Bash

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 les paramètres utilisés:

mysql -u user -h Host

Quel est le meilleur moyen d’obtenir la valeur -u param et la valeur -h param par indicateur au lieu de par position?

247
Stann

C'est le langage 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
  • while loop examine tous les arguments fournis, en faisant correspondre leurs valeurs dans une instruction case
  • shift enlève le premier. Vous pouvez déplacer plusieurs fois dans une instruction case pour prendre plusieurs valeurs.
254
Flexo

Cet exemple utilise la commande intégrée getopts de Bash et provient du Guide de style de 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 tout est beaucoup plus lisible et montre quelles sont les options acceptées
  • code plus propre; pas de comptage du nombre de paramètres et de décalage
  • vous pouvez joindre des options (par exemple, -a -b -c-abc)

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

362
Dennis

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.

44
Shizzmo

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 intégrés de Bash 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 afin que vous obteniez le comportement approprié, quel que soit l'ordre dans lequel vous transmettez les paramètres.

Exemple :

 DOSOMETHING -last "Adios" -first "Hola"

Sortie:

 First argument : Hola
 Last argument : Adios

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

Merci!

Edit: enregistrez ceci en tant que fichier puis exécutez-le en tant que 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";
8
Matias Barrios

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 d’autoriser les balises compilées - i = "example.jpg" ou séparez - i example.jpg méthodes de transmission 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";
5
Robert McMahan

J'aime bien la réponse de Robert McMahan, car il semble plus facile de transformer des fichiers d'inclusion partageables que vos scripts peuvent utiliser. Mais il semble y avoir un défaut avec la ligne if [[ -n ${variables[$argument_label]} ]] en lançant le message, "variables: bad array subscript". Je n'ai pas le représentant à commenter, et je doute que ce soit la "solution" appropriée, mais envelopper cette if dans if [[ -n $argument_label ]] ; then le nettoie.

Voici le code avec lequel je me suis retrouvé. Si vous connaissez un meilleur moyen, veuillez ajouter un commentaire à la réponse de Robert.

Fichier d'inclusion "flags-declares.sh"

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

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

Fichier d'inclusion "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";
3
Michael

Si vous connaissez Python argparse et que vous appelez python pour analyser les arguments bash, voici un morceau de code que j'ai trouvé vraiment utile et très 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
1
Linh

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 -n lors de l'exécution du script.

Exécute le script en tant que tel:

$ bash helloworld.sh -n 'World'

sortie

$ Hello World!

Notes

Si vous souhaitez utiliser plusieurs paramètres:

  1. étendre while getops "n:" arg: do avec plus de paramètres tels que while getops "n:o:p:" arg: do
  2. étendre le commutateur de cas avec des affectations variables supplémentaires. Tels que o) Option=$OPTARG et p) Parameter=$OPTARG
0
pijemcolu