Comment créer un menu de sélection dans un script shell?

134

Je crée un script bash simple et je veux y créer un menu de sélection, comme ceci:

$./script

echo "Choose your option:"

1) Option 1  
2) Option 2  
3) Option 3  
4) Quit  

Et selon le choix de l'utilisateur, je souhaite que différentes actions soient exécutées. Je suis un scripteur de script bash, j'ai cherché des réponses sur le Web, mais je n'ai rien de vraiment concret.

Daniel Rodrigues
la source
1
La question est ancienne et protégée, mais j'utilise fzf. Essayez seq 10 | fzf. L'inconvénient est que fzf n'est pas installé par défaut. Vous pouvez trouver fzf ici: github.com/junegunn/fzf
Lynch

Réponses:

152
#!/bin/bash
# Bash Menu Script Example

PS3='Please enter your choice: '
options=("Option 1" "Option 2" "Option 3" "Quit")
select opt in "${options[@]}"
do
    case $opt in
        "Option 1")
            echo "you chose choice 1"
            ;;
        "Option 2")
            echo "you chose choice 2"
            ;;
        "Option 3")
            echo "you chose choice $REPLY which is $opt"
            ;;
        "Quit")
            break
            ;;
        *) echo "invalid option $REPLY";;
    esac
done

Ajoutez des breakinstructions chaque fois que vous avez besoin de la selectboucle pour sortir. Si a breakn'est pas exécuté, l' selectinstruction est bouclée et le menu est à nouveau affiché.

Dans la troisième option, j'ai inclus les variables définies par l' selectinstruction afin de démontrer que vous avez accès à ces valeurs. Si vous le choisissez, il générera:

you chose choice 3 which is Option 3

Vous pouvez voir que $REPLYcontient la chaîne que vous avez entrée à l'invite. Il est utilisé comme un index dans le tableau ${options[@]}comme si le tableau était basé sur 1. La variable $optcontient la chaîne de cet index dans le tableau.

Notez que les choix peuvent être une simple liste directement dans l’ selecténoncé suivant:

select opt in foo bar baz 'multi word choice'

mais vous ne pouvez pas mettre une telle liste dans une variable scalaire à cause des espaces dans l'un des choix.

Vous pouvez également utiliser la suppression de fichier si vous choisissez parmi les fichiers:

select file in *.tar.gz
Dennis Williamson
la source
@Abdull: C'est le comportement souhaité. L'option "Quitter" exécute un breakqui sort de la selectboucle. Vous pouvez ajouter breakn'importe où vous en avez besoin. Le manuel de Bash indique "Les commandes sont exécutées après chaque sélection jusqu'à l'exécution d'une commande d'interruption. La commande de sélection est alors terminée."
Dennis Williamson
FWIW, lancer ceci sur 14.04 avec GNU bash 4.3.11 produit des erreurs sur la ligne 9: ./test.sh: line 9: syntax error near unexpected token "Option 1" './test.sh: ligne 9: `" Option 1 ")'`
Brian Morton
@BrianMorton: Je ne peux pas reproduire cela. Il est probable que vous manquiez d'une citation ou d'un autre caractère plus tôt dans le script (ou que vous en ayez un supplémentaire).
Dennis Williamson
2
@dtmland: PS3est l'invite de la selectcommande. Il est utilisé automatiquement et ne nécessite pas de référence explicite. PS3et selectdocumentation.
Dennis Williamson
1
@Christian: Non, ça ne devrait pas (mais ça le pourrait si j'utilisais les indices de $optionsdans la casedéclaration au lieu des valeurs). Je pense que l’utilisation des valeurs documente mieux la fonctionnalité des sections de la casedéclaration.
Dennis Williamson
56

Ce n'est pas une nouvelle réponse en soi , mais comme il n'y a pas encore de réponse acceptée, voici quelques conseils et astuces de codage, à la fois pour select et zenity:

title="Select example"
prompt="Pick an option:"
options=("A" "B" "C")

echo "$title"
PS3="$prompt "
select opt in "${options[@]}" "Quit"; do 

    case "$REPLY" in

    1 ) echo "You picked $opt which is option $REPLY";;
    2 ) echo "You picked $opt which is option $REPLY";;
    3 ) echo "You picked $opt which is option $REPLY";;

    $(( ${#options[@]}+1 )) ) echo "Goodbye!"; break;;
    *) echo "Invalid option. Try another one.";continue;;

    esac

done

while opt=$(zenity --title="$title" --text="$prompt" --list \
                   --column="Options" "${options[@]}"); do

    case "$opt" in
    "${options[0]}" ) zenity --info --text="You picked $opt, option 1";;
    "${options[1]}" ) zenity --info --text="You picked $opt, option 2";;
    "${options[2]}" ) zenity --info --text="You picked $opt, option 3";;
    *) zenity --error --text="Invalid option. Try another one.";;
    esac

done

À noter:

  • Les deux boucleront jusqu'à ce que l'utilisateur choisisse explicitement Quit (ou Annuler pour zenity). C'est une bonne approche pour les menus de script interactifs: une fois le choix sélectionné et l'action exécutée, le menu est présenté à nouveau pour un autre choix. Si le choix est censé être ponctuel, utilisez-le breakaprès esac(l’approche zenity pourrait également être réduite)

  • Les deux casesont basés sur des indices plutôt que sur des valeurs. Je pense que c'est plus facile à coder et à maintenir

  • Le tableau est également utilisé pour l' zenityapproche.

  • L'option "Quitter" ne figure pas parmi les options initiales d'origine. Il est "ajouté" si nécessaire, afin que votre tableau reste propre. Après tout, "Quitter" n'est de toute façon pas nécessaire pour zenity, l'utilisateur peut simplement cliquer sur "Annuler" (ou fermer la fenêtre) pour quitter. Remarquez comment les deux utilisent le même tableau d’options intactes.

  • PS3et REPLYvars ne peuvent pas être renommés. selectest codé en dur pour les utiliser. Toutes les autres variables du script (opt, options, prompt, title) peuvent avoir les noms de votre choix, à condition de faire les ajustements.

MestreLion
la source
Explication géniale. Je vous remercie. Cette question occupe toujours une place importante sur Google. Dommage qu'elle soit fermée.
MountainX
Vous pouvez utiliser la même casestructure de la selectversion que vous utilisez pour la zenityversion: case "$opt" in . . . "${options[0]}" ) . . .( au lieu de $REPLYet les indices 1, 2et 3).
Dennis Williamson
@ DennisWilliamson, oui, et dans le "vrai" code, il serait préférable d'utiliser la même structure dans les deux cas. J'ai voulu intentionnellement montrer la relation entre les $REPLYindex et les valeurs.
MestreLion
55

En utilisant dialog, la commande ressemblerait à ceci:

dialog --clear --backtitle "Backtitle here" --title "Title here" --menu "Choisissez l'une des options suivantes:" 15 40 4 \
1 "Option 1" \
2 "Option 2" \
3 "Option 3"

entrez la description de l'image ici

En le mettant dans un script:

#!/bin/bash

HEIGHT=15
WIDTH=40
CHOICE_HEIGHT=4
BACKTITLE="Backtitle here"
TITLE="Title here"
MENU="Choose one of the following options:"

OPTIONS=(1 "Option 1"
         2 "Option 2"
         3 "Option 3")

CHOICE=$(dialog --clear \
                --backtitle "$BACKTITLE" \
                --title "$TITLE" \
                --menu "$MENU" \
                $HEIGHT $WIDTH $CHOICE_HEIGHT \
                "${OPTIONS[@]}" \
                2>&1 >/dev/tty)

clear
case $CHOICE in
        1)
            echo "You chose Option 1"
            ;;
        2)
            echo "You chose Option 2"
            ;;
        3)
            echo "You chose Option 3"
            ;;
esac
Alaa Ali
la source
Je voudrais souligner que je mets la ligne TERMINAL=$(tty)en haut de mon script, puis dans la CHOICEdéfinition de la variable que j'ai changé 2>&1 >/dev/ttypour 2>&1 >$TERMINALéviter les problèmes de redirection si le script a été exécuté dans un contexte autre terminal.
Shrout1
1
Que fait le --backtitleparamètre?
TRiG
2
C'est le titre de l'écran bleu en arrière-plan. Vous pouvez le voir dans le coin supérieur gauche de la capture d'écran où il est écrit «Backtitle here».
Alaa Ali
14

Vous pouvez utiliser ce script simple pour créer des options

#! / bin / bash
echo "sélectionne l'opération ************"
echo "1) opération 1"
echo "2) opération 2"
echo "3) operation 3"
echo "4) operation 4" 
lire n case $ n dans 1) echo "Vous avez choisi l'option 1" ;; 2) echo "Vous avez choisi l'option 2" ;; 3) echo "Vous avez choisi l'option 3" ;; 4) echo "Vous avez choisi l'option 4" ;; *) echo "option invalide" ;; esac

Jibin
la source
8

J'ai une autre option qui est un mélange de ces réponses, mais ce qui le rend agréable est que vous n'avez qu'à appuyer sur une touche puis le script continue grâce à l' -noption de lecture. Dans cet exemple, nous vous invitons à arrêter, à redémarrer ou simplement à quitter le script en ANStant que variable. L'utilisateur doit uniquement appuyer sur E, R ou S. La valeur par défaut est également définie pour quitter. Par conséquent, si vous appuyez sur la touche Entrée, le script va sortir.

read -n 1 -p "Would you like to exit, reboot, or shutdown? (E/r/s) " ans;

case $ans in
    r|R)
        sudo reboot;;
    s|S)
        sudo poweroff;;
    *)
        exit;;
esac
HarlemSquirrel
la source
7

Etant donné que ceci est destiné à Ubuntu, vous devez utiliser le backend que debconf est configuré pour utiliser. Vous pouvez trouver le backend debconf avec:

sudo -s "echo get debconf/frontend | debconf-communicate"

S'il dit "dialogue", il utilisera probablement whiptailou dialog. Sur Lucid c'est whiptail.

Si cela échoue, utilisez bash "select" comme expliqué par Dennis Williamson.

Li Lo
la source
3
C’est probablement exagéré pour cette question, mais +1 pour mentionner whiptail et dialog! Je n'étais pas au courant de ces commandes ... très gentil!
MestreLion
7
#! / bin / sh
Afficher le menu(){
    normal = `echo" \ 033 [m "`
    menu = `echo" \ 033 [36m "` #Blue
    nombre = `echo" \ 033 [33m "` #yellow
    bgred = `echo" \ 033 [41m "`
    fgred = `echo" \ 033 [31m "`
    printf "\ n $ {menu} ***************************************** *** $ {normal} \ n "
    printf "$ {menu} ** $ {numéro} 1) $ {menu} Monter la boîte de transfert $ {normal} \ n"
    printf "$ {menu} ** $ {numéro} 2) $ {menu} Monter un lecteur USB 500 Go $ {normal} \ n"
    printf "$ {menu} ** $ {numéro} 3) $ {menu} Redémarrez Apache $ {normal} \ n"
    printf "$ {menu} ** $ {numéro} 4) $ {menu} ssh Frost Serveur TomCat $ {normal} \ n"
    printf "$ {menu} ** $ {numéro} 5) $ {menu} Quelques autres commandes $ {normal} \ n"
    printf "$ {menu} ******************************************* * $ {normal} \ n "
    printf "Veuillez entrer une option de menu et entrez ou $ {fgred} x pour quitter. $ {normal}"
    lire opt
}

option_picked () {
    msgcolor = `echo" \ 033 [01; 31m "` # gras rouge
    normal = `echo" \ 033 [00; 00m "` # normal white
    message = $ {@: - "$ {normal} Erreur: aucun message transmis"}
    printf "$ {msgcolor} $ {message} $ {normal} \ n"
}

clair
Afficher le menu
tandis que [$ opt! = '']
    faire
    si [$ opt = '']; ensuite
      sortie;
    autre
      case $ opt in
        1) effacer;
            option_picked "Option 1 choisie";
            printf "sudo mount / dev / sdh1 / mnt / DropBox /; #le 3 téraoctet";
            Afficher le menu;
        ;;
        2) effacer;
            option_picked "Option 2 choisie";
            printf "sudo mount / dev / sdi1 / mnt / usbDrive; #Le lecteur 500 Go";
            Afficher le menu;
        ;;
        3) effacer;
            option_picked "Option 3 choisie";
            printf "sudo service apache2 restart";
            Afficher le menu;
        ;;
        4) effacer;
            option_picked "Option 4 choisie";
            printf "ssh lmesser @ -p 2010";
            Afficher le menu;
        ;;
        x) sortie;
        ;;
        \ n) sortir;
        ;;
        *)clair;
            option_picked "Choisissez une option dans le menu";
            Afficher le menu;
        ;;
      esac
    Fi
terminé
Alex Lucard
la source
2
Je sais que c’est vieux, mais il faut que la première ligne lise #! / Bin / bash pour compiler.
JClar
Révision du code: il $manque la $optvariable dans la whiledéclaration. La ifdéclaration est redondante. Indentation incohérente. Utiliser menuà certains endroits où il devrait être 'show_menu . show_menu` pourrait être placé en haut de la boucle au lieu d'être répété dans chacun d'eux case. Indentation incohérente. Utilisation combinée de crochets simples et doubles. Utilisation de séquences ANSI codées en dur au lieu de tput. L'utilisation de noms de variables en majuscules n'est pas recommandée. FGREDdevrait être appelé bgred. Utilisation de backticks au lieu de $(). La définition de fonction doit être cohérente et ne pas utiliser ...
Dennis Williamson
... les deux formes ensemble. Les points-virgules terminaux ne sont pas nécessaires. Quelques couleurs, etc., définies deux fois. Le cas avec \nne sera jamais exécuté. Peut-être plus.
Dennis Williamson
6

J'ai utilisé Zenity, qui semble toujours être présent dans Ubuntu, fonctionne très bien et dispose de nombreuses fonctionnalités. Ceci est un croquis d'un menu possible:

#! /bin/bash

selection=$(zenity --list "Option 1" "Option 2" "Option 3" --column="" --text="Text above column(s)" --title="My menu")

case "$selection" in
"Option 1")zenity --info --text="Do something here for No1";;
"Option 2")zenity --info --text="Do something here for No2";;
"Option 3")zenity --info --text="Do something here for No3";;
esac
LazyEchidna
la source
Oops! désolé pour l'apparence de cet extrait, première publication et semble devoir désactiver HTML peut
LazyEchidna
Mieux, activez 'échantillon de code' dans l'édition, désolée
LazyEchidna
5

Menu fantaisie Bash

Essayez-le d'abord, puis visitez ma page pour une description détaillée ... Pas besoin de bibliothèques ou de programmes externes comme dialog ou zenity ...

#/bin/bash
# by oToGamez
# www.pro-toolz.net

      E='echo -e';e='echo -en';trap "R;exit" 2
    ESC=$( $e "\e")
   TPUT(){ $e "\e[${1};${2}H";}
  CLEAR(){ $e "\ec";}
  CIVIS(){ $e "\e[?25l";}
   DRAW(){ $e "\e%@\e(0";}
  WRITE(){ $e "\e(B";}
   MARK(){ $e "\e[7m";}
 UNMARK(){ $e "\e[27m";}
      R(){ CLEAR ;stty sane;$e "\ec\e[37;44m\e[J";};
   HEAD(){ DRAW
           for each in $(seq 1 13);do
           $E "   x                                          x"
           done
           WRITE;MARK;TPUT 1 5
           $E "BASH SELECTION MENU                       ";UNMARK;}
           i=0; CLEAR; CIVIS;NULL=/dev/null
   FOOT(){ MARK;TPUT 13 5
           printf "ENTER - SELECT,NEXT                       ";UNMARK;}
  ARROW(){ read -s -n3 key 2>/dev/null >&2
           if [[ $key = $ESC[A ]];then echo up;fi
           if [[ $key = $ESC[B ]];then echo dn;fi;}
     M0(){ TPUT  4 20; $e "Login info";}
     M1(){ TPUT  5 20; $e "Network";}
     M2(){ TPUT  6 20; $e "Disk";}
     M3(){ TPUT  7 20; $e "Routing";}
     M4(){ TPUT  8 20; $e "Time";}
     M5(){ TPUT  9 20; $e "ABOUT  ";}
     M6(){ TPUT 10 20; $e "EXIT   ";}
      LM=6
   MENU(){ for each in $(seq 0 $LM);do M${each};done;}
    POS(){ if [[ $cur == up ]];then ((i--));fi
           if [[ $cur == dn ]];then ((i++));fi
           if [[ $i -lt 0   ]];then i=$LM;fi
           if [[ $i -gt $LM ]];then i=0;fi;}
REFRESH(){ after=$((i+1)); before=$((i-1))
           if [[ $before -lt 0  ]];then before=$LM;fi
           if [[ $after -gt $LM ]];then after=0;fi
           if [[ $j -lt $i      ]];then UNMARK;M$before;else UNMARK;M$after;fi
           if [[ $after -eq 0 ]] || [ $before -eq $LM ];then
           UNMARK; M$before; M$after;fi;j=$i;UNMARK;M$before;M$after;}
   INIT(){ R;HEAD;FOOT;MENU;}
     SC(){ REFRESH;MARK;$S;$b;cur=`ARROW`;}
     ES(){ MARK;$e "ENTER = main menu ";$b;read;INIT;};INIT
  while [[ "$O" != " " ]]; do case $i in
        0) S=M0;SC;if [[ $cur == "" ]];then R;$e "\n$(w        )\n";ES;fi;;
        1) S=M1;SC;if [[ $cur == "" ]];then R;$e "\n$(ifconfig )\n";ES;fi;;
        2) S=M2;SC;if [[ $cur == "" ]];then R;$e "\n$(df -h    )\n";ES;fi;;
        3) S=M3;SC;if [[ $cur == "" ]];then R;$e "\n$(route -n )\n";ES;fi;;
        4) S=M4;SC;if [[ $cur == "" ]];then R;$e "\n$(date     )\n";ES;fi;;
        5) S=M5;SC;if [[ $cur == "" ]];then R;$e "\n$($e by oTo)\n";ES;fi;;
        6) S=M6;SC;if [[ $cur == "" ]];then R;exit 0;fi;;
 esac;POS;done
utilisateur360154
la source
1
Cela ne fonctionne qu'avec bash, plutôt cool.
Layton Everson
2
agréable! "alors visitez ma page pour une description détaillée" y at-il un lien?
Chêne
2

Il y a déjà la même question dans serverfault répondu. La solution utilise whiptail .

txwikinger
la source
Merci, mais comme mon script est destiné à la consommation grand public, je ne souhaite pas qu’il ait des dépendances supplémentaires. Mais je vais marquer cela pour une utilisation future, qui sait.
Daniel Rodrigues
-1

En supposant que vous souhaitiez utiliser un menu de script shell simple (pas d’interface utilisateur sophistiquée), consultez l’exemple de menu disponible à l’ adresse http://www.tldp.org/LDP/abs/html/testbranch.html .

João Pinto
la source
1
Comme beaucoup d'autres extraits du guide avancé de script bash, ces extraits contiennent des bogues et de mauvaises pratiques.
geirha