Wie kann ich ein Auswahlmenü in einem Shell-Skript erstellen?

134

Ich erstelle ein einfaches Bash-Skript und möchte darin ein Auswahlmenü erstellen, wie folgt:

$./script

echo "Choose your option:"

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

Und je nach Wahl des Benutzers möchte ich, dass verschiedene Aktionen ausgeführt werden. Ich bin ein Bash-Shell-Scripting-Neuling. Ich habe im Web nach Antworten gesucht, aber nichts wirklich Konkretes gefunden.

Daniel Rodrigues
quelle
1
Die Frage ist alt und geschützt, aber ich benutze fzf. Versuchen Sie es seq 10 | fzf. Der Nachteil ist, dass fzf nicht standardmäßig installiert ist. Sie finden fzf hier: github.com/junegunn/fzf
Lynch

Antworten:

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

Fügen Sie breakAnweisungen hinzu, wo immer Sie die selectSchleife zum Beenden benötigen . Wenn a breaknicht ausgeführt wird, wird die selectAnweisung wiederholt und das Menü erneut angezeigt.

In der dritten Option habe ich Variablen eingefügt, die durch die selectAnweisung festgelegt werden, um zu demonstrieren, dass Sie Zugriff auf diese Werte haben. Wenn Sie es auswählen, wird Folgendes ausgegeben:

you chose choice 3 which is Option 3

Sie sehen, dass $REPLYdie Zeichenfolge enthalten ist, die Sie an der Eingabeaufforderung eingegeben haben. Es wird als Index für das Array verwendet, ${options[@]}als ob das Array 1-basiert wäre. Die Variable $optenthält die Zeichenfolge aus diesem Index im Array.

Beachten Sie, dass die Auswahlmöglichkeiten eine einfache Liste direkt in der selectAnweisung sein können:

select opt in foo bar baz 'multi word choice'

Sie können eine solche Liste jedoch nicht in eine skalare Variable einfügen, da in einer der Auswahlmöglichkeiten Leerzeichen enthalten sind.

Sie können auch File Globbing verwenden, wenn Sie zwischen folgenden Dateien wählen:

select file in *.tar.gz
Dennis Williamson
quelle
@Abdull: Das ist das beabsichtigte Verhalten. Die Option "Beenden" führt einen breakaus der selectSchleife ausbrechenden Befehl aus. Sie können hinzufügen, breakwo immer Sie es brauchen. Das Bash-Handbuch besagt: "Die Befehle werden nach jeder Auswahl ausgeführt, bis ein Unterbrechungsbefehl ausgeführt wird. Zu diesem Zeitpunkt ist der Auswahlbefehl abgeschlossen."
Dennis Williamson
FWIW, das dies auf 14.04 mit GNU Bash 4.3.11 ausführt, erzeugt Fehler in Zeile 9: ./test.sh: line 9: syntax error near unexpected token "Option 1" ./test.sh: Zeile 9: "Option 1")
Brian Morton
@ BrianMorton: Ich kann das nicht reproduzieren. Vermutlich fehlt Ihnen ein Zitat oder ein anderes Zeichen früher im Skript (oder Sie haben ein Extra).
Dennis Williamson
2
@dtmland: PS3ist die Eingabeaufforderung für den selectBefehl. Es wird automatisch verwendet und muss nicht explizit referenziert werden. PS3und selectDokumentation.
Dennis Williamson
1
@ Christian: Nein, sollte es nicht (aber es könnte sein, wenn ich die Indizes von $optionsin der caseAnweisung anstelle der Werte verwende). Ich denke, dass die Verwendung der Werte die Funktionalität der Abschnitte der caseAnweisung besser dokumentiert .
Dennis Williamson
56

Per se keine neue Antwort , aber da es noch keine akzeptierte Antwort gibt, finden Sie hier ein paar Tipps und Tricks zum Codieren, sowohl für Select als auch für 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

Erwähnenswert:

  • Beide werden wiederholt, bis der Benutzer explizit Beenden (oder Abbrechen für Zenity) wählt. Dies ist ein guter Ansatz für interaktive Skriptmenüs: Nachdem eine Auswahl getroffen und eine Aktion ausgeführt wurde, wird das Menü erneut für eine andere Auswahl angezeigt. Wenn die Auswahl nur einmalig sein soll, verwenden Sie einfach breakafter esac(der Zenity-Ansatz könnte auch weiter reduziert werden).

  • Beide casesind eher indexbasiert als wertbasiert. Ich denke, das ist einfacher zu programmieren und zu warten

  • Array wird auch für die zenityAnnäherung verwendet.

  • Die Option "Beenden" gehört nicht zu den ursprünglichen Optionen. Es wird bei Bedarf "hinzugefügt", damit Ihr Array sauber bleibt. Schließlich ist "Beenden" für Zenity sowieso nicht erforderlich. Der Benutzer kann einfach auf "Abbrechen" klicken (oder das Fenster schließen), um das Programm zu beenden. Beachten Sie, dass beide das gleiche, unberührte Optionsfeld verwenden.

  • PS3und REPLYVariablen können nicht umbenannt werden. selectist hartcodiert, um diese zu verwenden. Alle anderen Variablen im Skript (opt, options, prompt, title) können beliebige Namen haben, sofern Sie die Anpassungen vornehmen

MestreLion
quelle
Geniale Erklärung. Danke. Diese Frage hat bei Google immer noch einen hohen Stellenwert. Schade, dass sie geschlossen ist.
MountainX
Sie könnten die gleiche verwenden caseStruktur für die selectVersion , die Sie für die Verwendung von zenityVersion: case "$opt" in . . . "${options[0]}" ) . . .(statt $REPLYund die Indizes 1, 2und 3).
Dennis Williamson
@DennisWilliamson, ja ich könnte, und in "echtem" Code wäre es vorzuziehen, in beiden Fällen die gleiche Struktur zu verwenden. Ich wollte absichtlich die Beziehung zwischen $REPLY, Indizes und Werten zeigen.
MestreLion
55

Mit dialogwürde der Befehl so aussehen:

dialog --clear --backtitle "Backtitle here" --title "Title here" --menu "Wählen Sie eine der folgenden Optionen:" 15 40 4 \
1 "Option 1" \
2 "Option 2" \
3 "Option 3"

Bildbeschreibung hier eingeben

Einfügen in ein Skript:

#!/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
quelle
Ich möchte darauf hinweisen, dass ich die Zeile TERMINAL=$(tty)oben in mein Skript eingefügt und dann in die CHOICEVariablendefinition geändert 2>&1 >/dev/ttyhabe 2>&1 >$TERMINAL, um Umleitungsprobleme zu vermeiden, wenn das Skript in einem anderen Terminalkontext ausgeführt wurde.
Shrout1
1
Was macht der --backtitleParameter?
TRiG
2
Es ist der Titel des Bluescreens im Hintergrund. Sie sehen es in der oberen linken Ecke des Screenshots mit der Aufschrift "Backtitle here".
Alaa Ali
14

Mit diesem einfachen Skript können Sie Optionen erstellen

#! / bin / bash
Echo "Wählen Sie die Operation ************"
Echo "1) Operation 1"
Echo "2) Operation 2"
Echo "3) Operation 3"
Echo "4) Operation 4" 
lies n case $ n in 1) Echo "Sie haben Option 1 gewählt" ;; 2) Echo "Sie haben Option 2 gewählt" ;; 3) Echo "Sie haben Option 3 gewählt" ;; 4) Echo "Sie haben Option 4 gewählt" ;; *) echo "ungültige Option" ;; esac

Jibin
quelle
8

Ich habe noch eine Option, die eine Mischung aus diesen Antworten ist, aber das Schöne daran ist, dass Sie nur eine Taste drücken müssen und das Skript dann dank der Lesemöglichkeit fortgesetzt wird -n. In diesem Beispiel werden Sie aufgefordert, das Skript herunterzufahren, neu zu starten oder einfach zu beenden, indem Sie ANSals Variable verwenden. Der Benutzer muss nur E, R oder S drücken. Ich habe auch die Standardeinstellung zum Beenden festgelegt. Wenn also die Eingabetaste gedrückt wird, wird das Skript ausgeführt wird verlassen.

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
quelle
7

Da dies auf Ubuntu abzielt, sollten Sie das Backend verwenden, für das debconf konfiguriert ist. Sie finden das debconf-Backend heraus mit:

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

Wenn dort "dialog" steht, wird wahrscheinlich whiptailoder verwendet dialog. Auf Lucid ist es whiptail.

Wenn dies fehlschlägt, verwenden Sie bash "select", wie von Dennis Williamson erklärt.

Li Lo
quelle
3
Dies ist wahrscheinlich übertrieben für diese Frage, aber +1 für das Erwähnen von Whiptail und Dialog! Mir waren diese Befehle nicht bekannt ... sehr süß!
MestreLion
7
#! / bin / sh
zeige das Menü(){
    normal = "Echo" \ 033 [m "
    menu = "Echo" \ 033 [36m "` #Blau
    number = "echo" \ 033 [33m "` #yellow
    bgred = "echo"
    fgred = "echo" \ 033 [31m "`
    printf "\ n $ {menu} ******************************************* *** $ {normal} \ n "
    printf "$ {menu} ** $ {number} 1) $ {menu} Dropbox einhängen $ {normal} \ n"
    printf "$ {menu} ** $ {number} 2) $ {menu} USB 500 Gig Drive einbinden $ {normal} \ n"
    printf "$ {menu} ** $ {number} 3) $ {menu} Apache neu starten $ {normal} \ n"
    printf "$ {menu} ** $ {number} 4) $ {menu} ssh Frost TomCat Server $ {normal} \ n"
    printf "$ {menu} ** $ {number} 5) $ {menu} Einige andere Befehle $ {normal} \ n"
    printf "$ {menu} ********************************************* * $ {normal} \ n "
    printf "Bitte geben Sie eine Menüoption ein und drücken Sie die Eingabetaste oder $ {fgred} x, um den Vorgang zu beenden. $ {normal}"
    Lesen Sie opt
}

option_picked () {
    msgcolor = "echo" \ 033 [01; 31m "` # fett rot
    normal = "Echo" "normal weiß"
    message = $ {@: - "$ {normal} Fehler: Keine Nachricht übergeben"}
    printf "$ {msgcolor} $ {message} $ {normal} \ n"
}

klar
zeige das Menü
während [$ opt! = '']
    machen
    if [$ opt = '']; dann
      Ausfahrt;
    sonst
      case $ opt in
        1) klar;
            option_picked "Option 1 ausgewählt";
            printf "sudo mount / dev / sdh1 / mnt / DropBox /; #Die 3 Terabyte";
            zeige das Menü;
        ;;
        2) klar;
            option_picked "Option 2 ausgewählt";
            printf "sudo mount / dev / sdi1 / mnt / usbDrive; #Das 500-Gig-Laufwerk";
            zeige das Menü;
        ;;
        3) klar;
            option_picked "Option 3 ausgewählt";
            printf "sudo service apache2 restart";
            zeige das Menü;
        ;;
        4) klar;
            option_picked "Option 4 ausgewählt";
            printf "ssh lmesser @ -p 2010";
            zeige das Menü;
        ;;
        x) Ausfahrt;
        ;;
        \ n) beenden;
        ;;
        *)klar;
            option_picked "Wähle eine Option aus dem Menü";
            zeige das Menü;
        ;;
      esac
    fi
getan
Alex Lucard
quelle
2
Ich weiß, dass dies alt ist, brauche aber die erste Zeile, um zu lesen, #! / Bin / bash zu kompilieren.
JClar
Codeüberprüfung: Das $fehlt in der $optVariablen in der whileAnweisung. Die ifAussage ist überflüssig. Inkonsistente Einrückung. Die Verwendung von menu"show_menu . show_menu" an einigen Stellen könnte am oberen Rand der Schleife stehen, anstatt in jeder Schleife wiederholt zu werden case. Inkonsistente Einrückung. Mischen von einfachen und doppelten eckigen Klammern. Verwenden von hartcodierten ANSI-Sequenzen anstelle von tput. Die Verwendung von All-Caps-Var-Namen wird nicht empfohlen. FGREDsollte angerufen werden bgred. Verwendung von Backticks anstelle von $(). Funktionsdefinition sollte konsistent sein und nicht verwenden ...
Dennis Williamson
... beide Formen zusammen. Terminal-Semikolons werden nicht benötigt. Einige Farben usw. sind doppelt definiert. Der Fall mit \nwird niemals ausgeführt. Vielleicht mehr.
Dennis Williamson
6

Ich habe Zenity verwendet, das in Ubuntu immer vorhanden zu sein scheint, sehr gut funktioniert und viele Funktionen hat. Dies ist eine Skizze eines möglichen Menüs:

#! /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
quelle
Hoppla! Entschuldigung für das Erscheinen dieses Snippets, das erstmalige Posten und muss anscheinend vielleicht HTML ausschalten
LazyEchidna
Besser, 'Codebeispiel' bei der Bearbeitung
aktiviert
5

Ausgefallene Speisekarte

Probieren Sie es zuerst aus, und besuchen Sie dann meine Seite, um eine detaillierte Beschreibung zu erhalten. Externe Bibliotheken oder Programme wie dialog oder zenity sind nicht erforderlich.

#/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
user360154
quelle
1
Das funktioniert nur mit bash, irgendwie cool.
Layton Everson
2
nett! "dann besuche meine Seite für eine detaillierte Beschreibung" gibt es einen Link?
Eiche
2

Es ist bereits die gleiche Frage im Serverfehler beantwortet. Die Lösung dort verwendet Whiptail .

txwikinger
quelle
Danke, aber da mein Skript für den allgemeinen Gebrauch bestimmt ist, möchte ich nicht, dass es zusätzliche Abhängigkeiten enthält. Aber das werde ich für die Zukunft vormerken, wer weiß.
Daniel Rodrigues
-1

Angenommen, Sie möchten ein einfaches Shell-Skriptmenü (keine ausgefallene Benutzeroberfläche) verwenden, lesen Sie das Menübeispiel unter http://www.tldp.org/LDP/abs/html/testbranch.html .

João Pinto
quelle
1
Wie bei vielen anderen Snippets im Handbuch für fortgeschrittene Bash-Skripterstellung enthalten diese Snippets Fehler und schlechte Praktiken.
Geirha