====== Fonctions bash : demander choix parmi liste ====== * Objet : Fonctions bash pour demander un choix parmi une liste d'options * Niveau requis : {{tag>avisé}} * Commentaires : //Création de scripts bash interactifs. // * Débutant, à savoir : [[:doc:systeme:commandes:le_debianiste_qui_papillonne|Utiliser GNU/Linux en ligne de commande, tout commence là !.]] :-) ===== Introduction ===== Collections de fonctions bash pour demander à l'utilisateur de faire un choix parmi une liste et rendre vos scripts plus interactif! Les fonctions permettent à chaque fois de définir un choix par défault (si réponse vide ou incorrecte) Vous trouverez: - Question binaire type Oui/Non avec exécution immédiate lors de la réponse de l'utilisateur. - Choix textuel avec un petit contrôle sur l'affichage - Choix dans liste numéroté Finalement, dans la dernière partie (exemple), vous trouverez un script compilant toutes les fonctions à des fins de démonstration. **bonus**: pour dé-commenter les scripts: cat script.sh | sed '/^\s*#/d' | sed 's/#.*$//' > script_sans commentaires.sh ===== Question binaire ===== ==== Utilisation ==== askYesNo "Voulez vous continuer ?" true Voulez vous continuer ? [O/n] o ==== Script ==== #!/bin/bash # Yes/No question with default askYesNo () { QUESTION=$1 DEFAULT=$2 if [ "$DEFAULT" = true ]; then # Valeur par défaut définie en paramètre OPTIONS="[O/n]" DEFAULT="o" else OPTIONS="[o/N]" DEFAULT="n" fi read -p "$QUESTION $OPTIONS " -n 1 -s -r INPUT # Execute au premier caractère (-n 1) INPUT=${INPUT:-${DEFAULT}} # Si $INPUT vide => remplace par $DEFAULT echo ${INPUT} if [[ "$INPUT" =~ ^[yYoO]$ ]]; then # True si y,Y,O ou o ANSWER=true else # Faux pour le reste ANSWER=false fi } ===== Choix parmi liste ===== ==== Utilisation ==== questionWithDefaultSimple "Votre choix?" "arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8" arg6 arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 Votre choix? [arg6] : ==== Script ==== #!/bin/bash questionWithDefaultSimple () { # Positional argument should strictly come before named arguments # 1er argument : Question à poser # 2e argument : Liste des options # 3e argument (optionnel) choix par défaut (sinon le premier item) QUESTION=$1 read -ra OPTIONS <<< "$2" # Transforme en array if [ -n "$3" ]; then # Si non donnée en entrèe => on prend le premier élément DEFAULT="$3" else DEFAULT="${OPTIONS[0]}" fi # Create pattern for printf COL_SIZE=15 # nombre de caractère pour chaque colonne FORMAT_STRING="%-${COL_SIZE}s %-${COL_SIZE}s %-${COL_SIZE}s %-${COL_SIZE}s\n" # 4 colonnes de $COL_SIZE caractère (méthode bourrin) # Show printf "$FORMAT_STRING" "${OPTIONS[@]}" # Mise en forme des options selon $FORMAT_STRING et affichage # Ask user printf "\x1b[1;32m$QUESTION\x1b[0m" # Question en gras + vert read -p " [$DEFAULT] : " -r INPUT # Demande d'input sans aller à la ligne INPUT=${INPUT:-${DEFAULT}} # Si l'input est vide, on rempli avec la valeur par défaut # Collect result if [[ "${OPTIONS[@]}" =~ "$INPUT" ]]; then # Si la réponse est une réponse valide, c-à-d que la valeur est présente dans $OPTIONS ANSWER=$INPUT # On garde la valeur dans ANSWER else ANSWER=$DEFAULT # Sinon on prend celle par défaut fi } ===== Choix parmi liste : complet ===== ==== Utilisation ==== questionWithDefaultComplet "Votre choix?" "arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8" -d 4 -c 3 arg1 arg2 arg3 arg4 [arg5] arg6 arg7 arg8 Votre choix? [arg5] : ==== Script ==== #!/bin/bash questionWithDefaultComplet () { # Positional argument should strictly come before named arguments # 1er argument : Question à poser # 2e argument : Liste des options # -c|--columns (optionnel) Nombre de colonnes pour l'affichage # -d|--default (optionnel) position dans la liste de l'option à mettre par défaut QUESTION=$1 read -ra OPTIONS <<< "$2" # Transforme la liste d'options en array read -ra OPTIONS_SHOW <<< "$2" # Même liste, mais destinée à être modifié pour l'affichage POS_DEFAULT=0 # Initialisation : position du choix par défaut à défaut de -d|--default NB_COLUMNS=4 # Initialisation ; nombre de colonnes à défaut de paramètre -c|--columns while [[ $# -gt 0 ]] ; do # Tant que le nombre d'argument ($#) n'est pas épuisé key="$1" # on cherche les clés correspondant aux options (-c et -d) case $key in -d|--default) # Une fois la clé touvée POS_DEFAULT="$2" # On sélectionne l'argument qui lui succède shift # on "consomme"/retire un argument de la liste $@ ;; -c|--columns) # Même chose pour l'autre clé NB_COLUMNS="$2" shift ;; esac shift # past value # On consomme un argument dans tout les cas done # Get new default option and format display OPTIONS_SHOW[$POS_DEFAULT]="[${OPTIONS_SHOW[$POS_DEFAULT]}]" # On entoure de [crochet] l'option par défault DEFAULT="${OPTIONS[$POS_DEFAULT]}" # On sauve sa valeur dans une variable # Get columns character length max_length=0 # Calcul de la taille minimum d'une colonne (toutes les options doivent tenir) for opt in ${OPTIONS[@]}; do # On itère sur les options opt_length=$(echo $opt | wc -c) # On compte le nombre de caractères if [ $opt_length -ge $max_length ]; then # Si on trouve une chaîne plus grande que la précédente "plus grande", max_length=$opt_length # on enregistre celle nouvelle taille fi done max_length=$(($max_length+1)) # 'tite marge supplémentaire # Create pattern for printf FORMAT_STRING="" # préparation de la mise en forme de l'affichage for i in $(seq $NB_COLUMNS); do # On concatène "le nombre de colonnes" * FORMAT_STRING="$FORMAT_STRING %-${max_length}s " # la taille min d'une colonne done FORMAT_STRING="$FORMAT_STRING \n" # Show printf "$FORMAT_STRING" "${OPTIONS_SHOW[@]}" # On affiche # Ask user printf "\x1b[1;32m$QUESTION\x1b[0m" # Un peu de couleur read -p " [$DEFAULT] : " -r INPUT # On demande une entrée à l'utilisateur INPUT=${INPUT:-${DEFAULT}} # Si il n'a rien rentré on substitue la valeur avec celle par défaut. # Collect result if [[ "${OPTIONS[@]}" =~ "$INPUT" ]]; then # Si la réponse est valide (dans la liste) ANSWER=$INPUT # On garde la valeur else ANSWER=$DEFAULT # Sinon, on prend celle par défaut fi } ===== Choix parmi liste numérotée : build-in select ===== la build-in select effectue une boucle infinie demandant de choisir parmi une liste, vous pouvez insérer une instruction **case** ou un nouveau **select** pour faire des sous menus. === Utilisation === numberWithSelect "Votre choix?" "arg1 arg2 arg3 arg4 arg5 arg6" Votre choix? 1) arg1 2) arg2 3) arg3 4) arg4 5) arg5 6) arg6 #? 5 Vous avec choisi arg5 === Script === numberWithSelect () { QUESTION=$1 read -ra OPTIONS <<< "$2" echo $QUESTION select ANSWER in ${OPTIONS[@]}; do if [ -n "$ANSWER" ]; then echo "Vous avec choisi $ANSWER" break else echo "Il n'y a pas de tel index!" fi done } ===== Choix parmi liste numérotée avec valeur par défaut ===== ==== Utilisation ==== numberedMenu "Votre choix?" "arg1 arg2 arg3 arg4 arg5 arg6" 2 1) arg1 2) arg2 3) arg3 4) arg4 5) arg5 6) arg6 Votre choix? [2] : ==== Script ==== #!/bin/bash numberedMenu () { # $1 = Question à poser # $2 = liste des éléments séparés par un espace # S3 (optionnal) = index de l'élément par défaut QUESTION=$1 read -ra OPTIONS <<< "$2" # Obtention de la liste de options if [ -n "$3" ]; then # Si donné par l'utilisateur (c-à-d variable non vide) DEFAULT=$3 else # Sinon, valeur par défaut DEFAULT=1 fi # Show printf "\n" for i in $(seq ${#OPTIONS[@]}); do # on compte le nombre d'options printf ' %3s %s\n' "$i)" "${OPTIONS[$(($i-1))]}" # On affiche la position dans la liste et la valeur de l'option done printf '\n' # ask user printf "\x1b[1;32m$QUESTION\x1b[0m" # Un peu de couleur (gras + vert) read -p " [$DEFAULT] : " -r INPUT # Demande valeur sans retour à la ligne INPUT=${INPUT:-${DEFAULT}} # Substitut par $DEFAULT si réponse vide # Collect result if [[ "$(seq ${#OPTIONS[@]})" =~ "$INPUT" ]]; then ANSWER="${OPTIONS[$(($INPUT-1))]}" else ANSWER="${OPTIONS[$(($DEFAULT-1))]}" fi } ===== Exemples ===== Ce fichier réunit toutes les fonctions définies plus haut et lance une série d'exemples #!/bin/bash # Yes/No question with default askYesNo () { QUESTION=$1 DEFAULT=$2 if [ "$DEFAULT" = true ]; then OPTIONS="[O/n]" DEFAULT="o" else OPTIONS="[o/N]" DEFAULT="n" fi read -p "$QUESTION $OPTIONS " -n 1 -s -r INPUT INPUT=${INPUT:-${DEFAULT}} echo ${INPUT} if [[ "$INPUT" =~ ^[yYoO]$ ]]; then ANSWER=true else ANSWER=false fi } questionWithDefault () { # Positional argument should strictly come before named arguments QUESTION=$1 read -ra OPTIONS <<< "$2" # Keep options list read -ra OPTIONS_SHOW <<< "$2" # Same list but highlight [default value] POS_DEFAULT=0 # position of default argument NB_COLUMNS=4 # nummber of element by line to display while [[ $# -gt 0 ]] ; do key="$1" case $key in -d|--default) POS_DEFAULT="$2" shift # past argument ;; -c|--columns) NB_COLUMNS="$2" shift ;; esac shift # past value done # Get new default option and format display OPTIONS_SHOW[$POS_DEFAULT]="[${OPTIONS_SHOW[$POS_DEFAULT]}]" DEFAULT="${OPTIONS[$POS_DEFAULT]}" # Get columns character length max_length=0 for opt in ${OPTIONS[@]}; do opt_length=$(echo $opt | wc -c) if [ $opt_length -ge $max_length ]; then max_length=$opt_length fi done max_length=$(($max_length+1)) # Create pattern for fstring FORMAT_STRING="" for i in $(seq $NB_COLUMNS); do FORMAT_STRING="$FORMAT_STRING %-${max_length}s " done FORMAT_STRING="$FORMAT_STRING \n" # Show printf "$FORMAT_STRING" "${OPTIONS_SHOW[@]}" # Ask user printf "\x1b[1;32m$QUESTION\x1b[0m" read -p " [$DEFAULT] : " -r INPUT INPUT=${INPUT:-${DEFAULT}} # Collect result if [[ "${OPTIONS[@]}" =~ "$INPUT" ]]; then ANSWER=$INPUT else ANSWER=$DEFAULT fi } questionWithDefaultSimple () { # Positional argument should strictly come before named arguments QUESTION=$1 read -ra OPTIONS <<< "$2" # Keep options list if [ -n "$3" ]; then DEFAULT="$3" else DEFAULT="${OPTIONS[0]}" fi # Create pattern for fstring COL_SIZE=15 FORMAT_STRING="%-${COL_SIZE}s %-${COL_SIZE}s %-${COL_SIZE}s %-${COL_SIZE}s\n" # Show printf "$FORMAT_STRING" "${OPTIONS[@]}" # Ask user printf "\x1b[1;32m$QUESTION\x1b[0m" read -p " [$DEFAULT] : " -r INPUT INPUT=${INPUT:-${DEFAULT}} # Collect result if [[ "${OPTIONS[@]}" =~ "$INPUT" ]]; then ANSWER=$INPUT else ANSWER=$DEFAULT fi } numberedMenu () { # $1 = question to ask # $2 = elements separted by space # S3 (optionnal) = index of defaut element QUESTION=$1 read -ra OPTIONS <<< "$2" # Keep options list if [ -n "$3" ]; then DEFAULT=$3 else DEFAULT=1 fi # Show printf "\n" for i in $(seq ${#OPTIONS[@]}); do printf ' %3s %s\n' "$i)" "${OPTIONS[$(($i-1))]}" done printf '\n' # ask user printf "\x1b[1;32m$QUESTION\x1b[0m" read -p " [$DEFAULT] : " -r INPUT INPUT=${INPUT:-${DEFAULT}} # Collect result if [[ "$(seq ${#OPTIONS[@]})" =~ "$INPUT" ]]; then ANSWER="${OPTIONS[$(($INPUT-1))]}" else ANSWER="${OPTIONS[$(($DEFAULT-1))]}" fi } echo "execution de:" echo 'askYesNo "Voulez vous continuer ?" true' echo '' askYesNo "Voulez vous continuer ?" true echo $ANSWER echo "" echo '-------------' echo "" askYesNo "Voulez vous continuer ?" false echo 'askYesNo "Voulez vous continuer ?" false' echo $ANSWER echo "" echo '-------------' echo "" echo "execution de:" echo 'split_args -n named_1 pos_1 -p_1 --named_long named pos_2' echo "" split_args -n named_1 pos_1 -p_1 --named_long named pos_2 echo "positional arguemnts : $POSITIONAL_ARGS" echo "named arguements : $NAMED_ARGS" echo "parameter arguments: $PARAM_ARGS" echo "" echo '-------------' echo "" echo "execution de:" echo 'questionWithDefault "Votre choix?" "arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14' echo "" questionWithDefault "Votre choix?" "arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14" echo $ANSWER echo "" echo '-------------' echo "" echo "execution de:" echo 'questionWithDefault "Votre choix?" "arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14" -d 8 -c 6' echo "" questionWithDefault "Votre choix?" "arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14" -d 8 -c 6 echo $ANSWER echo "" echo '-------------' echo "" echo "execution de:" echo 'questionWithDefaultSimple "Votre choix?" "arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14' arg6 echo "" questionWithDefaultSimple "Votre choix?" "arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14" arg6 echo $ANSWER echo "" echo '-------------' echo "" echo "execution de:" echo 'numberedMenu "Votre choix?" "arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14" 2' echo "" numberedMenu "Votre choix?" "arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14" 2 echo $ANSWER echo "" echo '-------------' echo ""