====== Gestion des paramètres en bash ====== * Objet : Gérer des paramètres utilisateur * Niveau requis : {{tag> avisé}} * Commentaires : Créer un script acceptant des paramètres de position, nommés et booléens. * Débutant, à savoir : [[:doc:systeme:commandes:le_debianiste_qui_papillonne|Utiliser GNU/Linux en ligne de commande, tout commence là !.]] :-) ===== Introduction ===== Voici quelques exemples permettant de créer des scripts bash possédants une gestion plus fine des paramètres d'entrées plutôt que la simple utilisation de leur position ($1, $2, ...) Avec ces scripts, vous pourrez supporter : - Des paramètres de position ./script.sh arg_1 - Des paramètres appelant une valeur ./script.sh -a valeur_a --long_parametre_b valeur_b - Des paramètres booléens ./script -o Le détail de fonctionnement des scripts est inclut en commentaire. **bonus** : pour dé-commenter les scripts : cat script.sh | sed '/^\s*#/d' | sed 's/#.*$//' > script_sans commentaires.sh ===== Paramètre booléen ===== === Utilisation === ./parametre_booleen.sh -o Vrai ./parametre_booleen.sh Faux === Script === #!/bin/bash # Paramètre booléén forme courte et longue -o/--long_option # On recherche une correspondance soit avec la forme courte, soit avec la longue. # On ajoute des espaces autour des variables pour une comparaison stricte et ne pas identifier de sous chaine. if [[ " $@ " =~ " -o " ]] || [[ " $@ " =~ " --long_option " ]] ; then PARAM=true # On peut affecter une valeur booléenne fi if [ -n "$PARAM" ]; then # Si la chaîne est non-nulle (~ que la variable a été crée (~ remplie)) echo "Vrai" else echo "Faux" fi ===== Paramètre & valeur ===== === Utilisation === ./parametre_nomme.sh -a val_a param_a = val_a ./parametre_nomme.sh param_a = === Script === #!/bin/bash # Paramètre attendant une valeur (named parameter) params=( "$@" ) # copie de du tableau des Paramètres for i in $(seq $#); do # On itère un à un sur tout les paramètres j=$(($i-1)) # seq commence à compter à 1, le premier paramètre est à 0. if [[ " ${params[$j]} " =~ " -a " ]] || [[ " ${params[$j]} " =~ " --long_a " ]] ; then PARAM_A=${params[$i]} # On récupère la paramètre succédant la clé "-a", soit "value_a" fi done echo "param_a = $PARAM_A" ===== Paramètres nommé et court (-p) avec getopts ===== **getopts** fait parti des buildins de bash, ce qui permet d'être sûr son implémentation (dès lors que l'on utilise bash) **getopts** ne prend que des arguments courts (-p), qui peuvent accepter un paramètre === Utilisation === ./getopts_simple.sh -v -a val_a -b val_b -o a=val_a b=val_b option_o=true === Script === #!/bin/bash # Paramètres "short" (-p) avec la build-in getopts help_msg () { echo "Usage: $(basename $0) [-h] [-v] [-a PARAM_A] [-b PARAM_B] [-o]" echo 'Description' echo ' -h display this help' echo ' -v verbose' echo ' -a PARAM_A Le paramètre A' echo ' -b PARAM_B Le paramètre B' echo ' -o option OPTION_O' } verbose_print () { # remplace un printf par un printf + condition local MESSAGE="${@}" if [[ "${VERBOSE}" == true ]];then # à condition que la variable globale $VERBOSE soit à true printf "${MESSAGE}\n" fi } # Si aucun argument donné, on affiche l'aide if [[ ${#} -eq 0 ]]; then help_msg exit 1 fi # Définition des paramètres optstring=":hva:b:o" # getopts cherchera les paramètres listés ('-h', 'v', 'a',...) # et attendra une valeur pour ceux suivis d'un ':' (-a value_a) # Voici la liste des variables utilisées par getopts : # OPTIND => index de l'argumetn # OPTARG => valeur de l'option si définie ( -a option => OPTARG="option" ) # OPTERR => 0 ou 1, si egal à 1, bash affiche les messages d'erreur (à 1 par défaut en début de boucle) OPTION_O=false # itère sur les paramètres type "-p", while getopts ${optstring} arg ; do # ne prend pas en compte les positionnel (arg) # ! le type long (--long) n'est pas accepté => cas '?)' case ${arg} in h) # Si $arg contient la valeur "h" help_msg exit 0 # Quitte sans erreur ;; # fin du test, ne teste pas les arguments suivant => retour en début de boucle v) VERBOSE='true' # variable globale à true (lue dans verbose_print) verbose_print "Mode prolixe activé." ;; a) # Si $arg contient la valeur "a" PARAM_A="${OPTARG}" # getopts a défini la variable $OPTARG qui contient le paramètre succédant à "-a" ;; b) PARAM_B="${OPTARG}" # même chose ;; o) OPTION_O='true' # On ne demande pas de valeur, on passe simplement la variable à true si l'option est présente ;; ?) # l'arugment n'a pas été défini echo "Option invalide: -${OPTARG}." echo help_msg echo ;; esac done printf "a=$PARAM_A \nb=$PARAM_B \noption_o=$OPTION_O\n" verbose_print "\nMessage si et seulement si drapeau '-v'" ===== Paramètres nommé et court (-p) et arguments positionnels avec getopts ===== **getops** ne gère pas les arguments positionnels, ce script permet leur implémentation === Utilisation === ./get_opt_example.sh -v pos_1 -a val_a -b val_b -o pos_2 Mode prolixe activé. a=val_a b=val_b option_o=true positionnal= pos_1 pos_2 === Script === optstring=":hva:b:o" OPTION_O=false while [ $# -gt 0 ]; do # Tant que $@ contient des éléments unset OPTIND # on supprime les variables $OPTIND (position de l'argument suivant dans $@) unset OPTARG # et $OPTARG définies dans le précédent tour de boucle while getopts ${optstring} arg; do # Même chose que précédemment case ${arg} in h) help_msg exit 0 ;; v) VERBOSE='true' verbose_print "Mode prolixe activé." ;; a) PARAM_A="${OPTARG}" ;; b) PARAM_B="${OPTARG}" ;; o) OPTION_O='true' ;; ?) echo "Option invalide: -${OPTARG}." echo help_msg echo ;; esac done # getopts itère jusqu'au premier argument positionnel # (c-à-d qui n'est pas sous forme '-p' ou ne correspond pas à une valeur (-p valeur)) # # Si en début de tour, # # $@ = -a value_a -b value_b arg_1 ... # # optargs s’arrête à arg_1, # on a traité 4 arguments : '-a', 'value_a', 'b' et "value_b', et getopts à défini # # OPTIND = 5 # # le prochain argument à traiter # shift $((OPTIND-1)) # On consomme 5-1 arguments avec le "shift", la liste des arguments $@ devient: # # $@ = arg_1 ... ARGS="${ARGS} $1 " # L'argument positionnel se retrouve en $1 shift # Une fois récupéré, on le consomme et on recommence la boucle jusqu'à ce qu'il n'y ai plus d'arguments done printf "a=$PARAM_A \nb=$PARAM_B \noption_o=$OPTION_O \npositionnal=$ARGS\n" verbose_print "Message si drapeau '-v'" ===== Tout type de paramètre ===== Pour permettre l'utilisation d'argument long (--long) ou créer une implémentation compatible POSIX, on peut se passer de getopts. === Utilisation === ./all_parameters_type.sh -a val_a --long_b val_b -o pos_1 pos_2 positional : pos_1 pos_2 param_a : val_a param_b : val_b option_o : true === Script === #!/bin/bash help_msg () { printf """ usage: script [-h] [-o] [-a NAMED_A] [-b NAMED_B] Description Paramètres optionnels -h, --help show this help message and exit -a NAMED_A, --long_a NAMED_A Le paramètre PARAM_A -b NAMED_B, --long_b NAMED_B Le paramètre PARAM_B -o, --option L'option OPTION_O """ } # get named parameters # On utilisera par la suite la fonction shift, qui "consomme" les arguments du tableau $@ # si $@ = "-a value_a --long_b value_b" # Executer : # shift # Consomme le premier argument ("-a"): # $@ = "value_a --long_b value_b" # # $# compte le nombre d'argument restants, et par conséquent est réduit de 1 à chaque "shift" # Prenons un exemple simple, "dans l'ordre d'écriture de la fonction" # Soit la commande: # # ./script.sh -a value_a --long_b value_b -o positional # # La variable $@ contient alors "-a value_a --long_ value_b -o positional" # On initialise la liste des paramètres de position POSITIONAL="" # On boucle tant qu'il y a des arguments while [ $# -gt 0 ] ; do # $# Nombre d'arguments passés en paramètre du script key="$1" # => key = "-a", le premier argument case $key in # Teste $key pour les cas suivant: -h|--help) help_msg # exécute la fonction help () exit 0 # et quitte le script ;; -a|--long_a) # la clé "-a" est reconnue PARAM_A="$2" # la valeur du paramètre PARAM_A vient juste après, soit $2 shift # consomme un argument, $@ ne contient plus que "value_a -b value_b" shift # consomme un deuxième argument, $@ = "-b value_b" ;; # On peut passer au tour suivant, avec $key prenant la valeur $1, soit "-b" -b|--long_b) # (2e tour) : $key à la valeur "-b" et est reconnue PARAM_B="$2" # Comme précédemment, on prend l'argument suivant $key/$1 soit $2 shift # Et on consomme la clé ("-b") shift # Puis la valeur du paramètres ("value_b") ;; -o|--option) # (3e tour) : la clé "-o" est reconnue OPTION_O=true # On affecte une valeur booléenne (on pourra la:tester avec [ -z OPTION_O ] par exemple ) shift # Cette fois ci on ne passe qu'un paramètres, puisqu'il n'y a pas de valeur donnée ;; *) # La clé ne correspond à aucun autre cas if ! [[ "$key" =~ "^-" ]]; then # Si elle ne commence pas par un "-" POSITIONAL="$POSITIONAL $key" # C'est un paramètre positionnel, on l'ajoute à la liste shift # On passe le paramètres (1 seul shift) else # Si la clé commence par "-" echo "Argument non défini : '$key'" # C'est quelle ne fait pas partie de la liste définie plus haut exit 1 # On arrête le programme fi esac done # FIN! Bien-sûr l'exemple est arrangé dans le "bon ordre", mais la position des arguments n'a pas d'importance. # Paramètres parsés printf "positional : $POSITIONAL \n" # Contient : "positional" printf "param_a : $PARAM_A \n" # Contient : "value_a" printf "param_b : $PARAM_B \n" # Contient : "value_b" printf "option_o : $OPTION_O \n" # Contient : "true" ===== Séparer les paramètres ===== Dans le cas de paramètres inconnus à l'avance, ou pour un script plus leger, cette fonction permet de trier les différents arguments en fonction de leur type supposé (positional(//arg//), named (//-n arg//), option (//-o//)) et les stocke dans 3 variables différentes. A defaut, un groupe `-n arg` est considéré comme **named** et non comme un **option** + **positional** Par exemple: - `--named value_1 value_2` => `--long value_1` est supposé comme groupe **named**, `value_2` est supposé **positional** - `--option --long value` => `--option` est considéré comme **option**, `--long value`, comme un groupe **named** tri sommaire des arguments (!robustesse!) === Utilisation === split_args -a value_a pos_1 -o --long_b value_b pos_2 POSITIONAL : pos_1 pos_2 NAMED : -a value_a --long_b value_b OPTION : -o === Script === split_args () { POSITIONAL_ARGS="" NAMED_ARGS="" OPTION_ARGS="" while [ $# -gt 0 ] ; do if [[ "$1" =~ ^- ]]; then if ! [[ "$2" =~ ^- ]] && [ -n "$2" ] ; then NAMED_ARGS="$NAMED_ARGS $1 $2" shift else OPTION_ARGS="$OPTION_ARGS $1" fi else POSITIONAL_ARGS="$POSITIONAL_ARGS $1" fi shift done }