Jak mogę utworzyć menu wyboru w skrypcie powłoki?

134

Tworzę prosty skrypt bash i chcę w nim utworzyć menu wyboru, takie jak to:

$./script

echo "Choose your option:"

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

I zgodnie z wyborem użytkownika chcę, aby były wykonywane różne działania. Jestem skryptem powłoki bash, szukałem odpowiedzi w Internecie, ale nie dostałem nic konkretnego.

Daniel Rodrigues
źródło
1
Pytanie jest stare i chronione, ale używam fzf. Spróbować seq 10 | fzf. Wadą jest to, że FZF nie jest domyślnie instalowany. Możesz znaleźć fzf tutaj: github.com/junegunn/fzf
Lynch

Odpowiedzi:

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

Dodaj breakinstrukcje wszędzie tam, gdzie potrzebujesz selectpętli, aby wyjść. Jeśli a breaknie zostanie wykonane, selectinstrukcja zapętli się, a menu zostanie ponownie wyświetlone.

W trzeciej opcji zawarłem zmienne ustawione przez selectinstrukcję, aby wykazać, że masz dostęp do tych wartości. Jeśli go wybierzesz, wyświetli:

you chose choice 3 which is Option 3

Możesz zobaczyć, że $REPLYzawiera ciąg wprowadzony w wierszu polecenia. Jest on używany jako indeks tablicy, ${options[@]}tak jakby tablica była oparta na 1. Zmienna $optzawiera ciąg z tego indeksu w tablicy.

Zauważ, że wybory mogą być prostą listą bezpośrednio w selectwyciągu, takim jak to:

select opt in foo bar baz 'multi word choice'

ale nie można umieścić takiej listy w zmiennej skalarnej ze względu na spacje w jednej z opcji.

Możesz także użyć globowania plików, jeśli wybierasz spośród plików:

select file in *.tar.gz
Dennis Williamson
źródło
@ Abdull: Takie jest zamierzone zachowanie. Opcja „Quit” wykonuje polecenie, breakktóre wychodzi z selectpętli. Możesz dodać breakgdziekolwiek potrzebujesz. Podręcznik Bash stwierdza: „Polecenia są wykonywane po każdym zaznaczeniu, aż do wykonania polecenia przerwania, w którym to momencie polecenie wyboru zostanie zakończone”.
Dennis Williamson,
FWIW, uruchomienie tego w dniu 14.04 z GNU bash 4.3.11 powoduje błędy w linii 9: ./test.sh: line 9: syntax error near unexpected token „Opcja 1” ./test.sh: linia 9: „„ Opcja 1 ”) ''
Brian Morton
@BrianMorton: Nie mogę tego odtworzyć. Prawdopodobnie brakuje Ci cytatu lub innej postaci wcześniej w skrypcie (lub masz dodatkową).
Dennis Williamson
2
@dtmland: PS3jest monitem o selectpolecenie. Jest używany automatycznie i nie wymaga jawnego odwoływania się do niego. PS3i selectdokumentacja.
Dennis Williamson
1
@Christian: Nie, nie powinno (ale mogłoby się tak stać, gdybym użył wskaźników $optionsw caseinstrukcji zamiast wartości). Myślę, że użycie wartości lepiej dokumentuje funkcjonalność sekcji caseinstrukcji.
Dennis Williamson
56

Nie jest to nowa odpowiedź sama w sobie , ale ponieważ nie ma jeszcze akceptowanej odpowiedzi, oto kilka porad i wskazówek dotyczących kodowania, zarówno dla select, jak i 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

Warte wspomnienia:

  • Oba będą się zapętlać, dopóki użytkownik nie wybierze Quit (lub Cancel dla zenity). Jest to dobre podejście w przypadku interaktywnych menu skryptów: po wybraniu wyboru i wykonaniu czynności ponownie pojawia się menu dla innego wyboru. Jeśli wybór ma być jednorazowy, po prostu użyj breakpo esac(podejście zenity można jeszcze bardziej zmniejszyć)

  • Oba casesą oparte na indeksie, a nie na wartości. Myślę, że łatwiej jest to kodować i utrzymywać

  • Tablica służy również do zenitypodejścia.

  • Opcja „Wyjdź” nie znajduje się wśród początkowych, oryginalnych opcji. W razie potrzeby jest „dodawany”, dzięki czemu macierz pozostaje czysta. W końcu „Quit” nie jest potrzebne do zenity, użytkownik może po prostu kliknąć „Cancel” (lub zamknąć okno), aby wyjść. Zauważ, że oba używają tej samej, nietkniętej tablicy opcji.

  • PS3i nieREPLY można zmienić nazwy vars . jest na stałe w użyciu. Wszystkie pozostałe zmienne w skrypcie (opcja, opcje, monit, tytuł) mogą mieć dowolne nazwy, pod warunkiem, że wykonasz odpowiednie zmianyselect

MestreLion
źródło
Niesamowite wyjaśnienie. Dziękuję Ci. To pytanie wciąż zajmuje wysokie miejsce w Google, więc szkoda, że ​​jest zamknięte.
MountainX
Można użyć tej samej casestruktury dla selectwersji, której używasz do zenitywersji: case "$opt" in . . . "${options[0]}" ) . . .(zamiast $REPLYi indeksów 1, 2i 3).
Dennis Williamson
@DennisWilliamson, tak, mógłbym, aw „prawdziwym” kodzie lepiej byłoby używać tej samej struktury w obu przypadkach. Celowo chciałem pokazać związek między $REPLYindeksami i wartościami.
MestreLion
55

Za dialogpomocą polecenia wyglądałoby to tak:

dialog - wyczyść - napis „Wstecz tutaj” - tytuł „Tu tytuł” ​​- menu „Wybierz jedną z następujących opcji:” 15 40 4 \
1 „Opcja 1” \
2 „Opcja 2” \
3 „Opcja 3”

wprowadź opis zdjęcia tutaj

Umieszczenie go w skrypcie:

#!/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
źródło
Chciałbym zauważyć, że mogę umieścić linię TERMINAL=$(tty)na szczycie mojego skryptu, a następnie w CHOICEdefinicji zmiennej zmieniłem 2>&1 >/dev/ttyaby 2>&1 >$TERMINALuniknąć problemów przekierowania jeśli skrypt został uruchomiony w innym kontekście terminala.
Shrout1
1
Co robi --backtitleparametr?
TRiG
2
To tytuł niebieskiego ekranu w tle. Możesz go zobaczyć w lewym górnym rogu zrzutu ekranu z napisem „Tu napis”.
Alaa Ali
14

Możesz użyć tego prostego skryptu do tworzenia opcji

#! / bin / bash
echo „wybierz operację ************”
echo „1) operacja 1”
echo „2) operacja 2”
echo „3) operacja 3”
echo „4) operacja 4” 
czytaj n skrzynka $ n w 1) echo „Wybrałeś opcję 1” ;; 2) echo „Wybrałeś opcję 2” ;; 3) echo „Wybrałeś opcję 3” ;; 4) echo „Wybrałeś opcję 4” ;; *) echo „nieprawidłowa opcja” ;; esac

jibin
źródło
8

Mam jeszcze jedną opcję, która jest mieszanką tych odpowiedzi, ale miło jest, że wystarczy nacisnąć tylko jeden klawisz, a następnie skrypt kontynuuje dzięki -nopcji odczytu. W tym przykładzie monitujemy o zamknięcie, ponowne uruchomienie lub po prostu zamknięcie skryptu za pomocą ANSnaszej zmiennej, a użytkownik musi tylko nacisnąć E, R lub S. Ustawiam też domyślną wartość wyjścia, więc jeśli zostanie naciśnięty klawisz Enter, skrypt wyjdzie.

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
Harlem Wiewiórka
źródło
7

Ponieważ jest to ukierunkowane na Ubuntu, powinieneś używać dowolnego skonfigurowanego do tego debconf zaplecza. Możesz znaleźć backend debconf za pomocą:

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

Jeśli powie „dialog”, prawdopodobnie używa whiptaillub dialog. Na Lucid to whiptail.

Jeśli to się nie powiedzie, użyj bash „select” zgodnie z wyjaśnieniami Dennisa Williamsona.

Li Lo
źródło
3
To prawdopodobnie przesada w przypadku tego pytania, ale +1 za wzmiankę o szyfrze i dialogu! Nie byłem świadomy tych poleceń ... bardzo słodko!
MestreLion,
7
#! / bin / sh
Pokaż menu(){
    normal = `echo" \ 033 [m ”`
    menu = `echo '\ 033 [36m' '#Blue
    number = `echo" \ 033 [33m "` #żółty
    bgred = `echo '\ 033 [41m' '
    fgred = `echo '\ 033 [31m' '
    printf "\ n $ {menu} ******************************************** *** $ {normal} \ n "
    printf "$ {menu} ** $ {numer} 1) $ {menu} Zamontuj dropbox $ {normal} \ n"
    printf "$ {menu} ** $ {numer} 2) $ {menu} Zamontuj napęd USB 500 Gig $ {normal} \ n"
    printf "$ {menu} ** $ {numer} 3) $ {menu} Uruchom ponownie Apache $ {normal} \ n"
    printf "$ {menu} ** $ {numer} 4) $ {menu} ssh Frost TomCat Server $ {normal} \ n"
    printf "$ {menu} ** $ {numer} 5) $ {menu} Niektóre inne polecenia $ {normal} \ n"
    printf "$ {menu} ******************************************** * $ {normal} \ n "
    printf "Wprowadź opcję menu i wprowadź lub $ {fgred} x, aby wyjść. $ {normal}"
    czytaj opt
}

Option_picked () {
    msgcolor = `echo" \ 033 [01; 31m "` # # pogrubiony czerwony
    normal = `echo '\ 033 [00; 00m"' # normalny biały
    message = $ {@: - "$ {normal} Błąd: nie przekazano wiadomości"}
    printf "$ {msgcolor} $ {message} $ {normal} \ n"
}

jasny
Pokaż menu
podczas gdy [$ opt! = '']
    zrobić
    if [$ opt = '']; następnie
      wyjście;
    jeszcze
      case $ opt in
        1) jasne;
            Option_picked "Wybrano opcję 1";
            printf "sudo mount / dev / sdh1 / mnt / DropBox /; #The 3 terabyte";
            Pokaż menu;
        ;;
        2) jasne;
            opcja_wybrana „Wybrano opcję 2”;
            printf "sudo mount / dev / sdi1 / mnt / usbDrive; # Napęd 500 gig";
            Pokaż menu;
        ;;
        3) jasne;
            opcja_wybrana „Wybrano opcję 3”;
            printf "sudo service apache2 restart";
            Pokaż menu;
        ;;
        4) jasne;
            Option_picked "Wybrano opcję 4";
            printf "ssh lmesser @ -p 2010";
            Pokaż menu;
        ;;
        x) wyjście;
        ;;
        \ n) wyjście;
        ;;
        *)jasny;
            Option_picked "Wybierz opcję z menu";
            Pokaż menu;
        ;;
      esac
    fi
gotowy
Alex Lucard
źródło
2
Wiem, że jest stary, ale potrzebuje pierwszego wiersza, aby przeczytać #! / Bin / bash, aby skompilować.
JClar,
Recenzja kodu: $Brakuje $optzmiennej w whileinstrukcji. ifStwierdzenie jest zbędny. Niespójne wcięcie. Używanie menuw niektórych miejscach, w których powinno być „show_menu . show_menu”, może być umieszczone na górze pętli zamiast powtarzania się w każdym z nich case. Niespójne wcięcie. Mieszanie użycia pojedynczych nawiasów kwadratowych i podwójnych. Używanie zakodowanych sekwencji ANSI zamiast tput. Używanie nazw zmiennych wielkimi literami nie jest zalecane. FGREDpowinien zostać nazwany bgred. Używaj backticks zamiast $(). Definicja funkcji powinna być spójna i nie używać ...
Dennis Williamson
... obie formy razem. Średniki końcowe nie są potrzebne. Niektóre kolory itp. Zdefiniowane dwukrotnie. Sprawa z \nnigdy nie zostanie wykonana. Być może więcej.
Dennis Williamson
6

Użyłem Zenity, która wydaje się zawsze dostępna w Ubuntu, działa bardzo dobrze i ma wiele możliwości. Oto szkic możliwego menu:

#! /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
źródło
Ups! przepraszam za pojawienie się tego fragmentu, opublikowanie go po raz pierwszy i chyba trzeba wyłączyć HTML
LazyEchidna
Lepiej, włączyłem „próbkę kodu” podczas edycji, przepraszam za to
LazyEchidna
5

Bash fantazyjne menu

Wypróbuj najpierw, a następnie odwiedź moją stronę, aby uzyskać szczegółowy opis ... Nie potrzebujesz zewnętrznych bibliotek ani programów, takich jak dialog czy 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
użytkownik360154
źródło
1
Działa to tylko z bash, trochę fajnie.
Layton Everson,
2
miły! „następnie odwiedź moją stronę w celu uzyskania szczegółowego opisu” czy jest tam link?
dąb
2

Nie jest już to samo pytanie w ServerFault odpowiedział. Tamtejsze rozwiązanie wykorzystuje whiptail .

txwikinger
źródło
Dzięki, ale ponieważ mój skrypt jest przeznaczony do powszechnego użytku, nie chcę, aby miał dodatkowe zależności. Ale dodam tę zakładkę do użytku w przyszłości, kto wie.
Daniel Rodrigues,
-1

Zakładając, że chcesz użyć zwykłego menu skryptowego powłoki (bez fantazyjnego interfejsu użytkownika), sprawdź przykład menu z http://www.tldp.org/LDP/abs/html/testbranch.html .

João Pinto
źródło
1
Podobnie jak w przypadku większości innych fragmentów zawartych w zaawansowanym przewodniku skryptów bash, fragmenty te zawierają błędy i złe praktyki.
geirha