Come posso creare un menu di selezione in uno script di shell?


134

Sto creando un semplice script bash e voglio creare un menu di selezione al suo interno, in questo modo:

$./script

echo "Choose your option:"

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

E secondo la scelta dell'utente, voglio eseguire diverse azioni. Sono un portatore di scripting bash shell, ho cercato sul web alcune risposte, ma non ho ottenuto nulla di veramente concreto.


1
La domanda è vecchia e protetta, ma io uso fzf. Prova seq 10 | fzf. Lo svantaggio è che fzf non è installato di default. Puoi trovare FZF qui: github.com/junegunn/fzf
Lynch

Risposte:


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

Aggiungi breakistruzioni ovunque sia necessario il selectciclo per uscire. Se a breaknon viene eseguito, l' selectistruzione scorre e il menu viene nuovamente visualizzato.

Nella terza opzione, ho incluso le variabili impostate selectdall'istruzione per dimostrare che hai accesso a tali valori. Se lo scegli, genererà:

you chose choice 3 which is Option 3

Puoi vedere che $REPLYcontiene la stringa che hai inserito al prompt. Viene utilizzato come indice nell'array ${options[@]}come se l'array fosse basato su 1. La variabile $optcontiene la stringa di quell'indice nell'array.

Si noti che le scelte potrebbero essere un semplice elenco direttamente selectnell'istruzione come questo:

select opt in foo bar baz 'multi word choice'

ma non puoi mettere un tale elenco in una variabile scalare a causa degli spazi in una delle scelte.

Puoi anche utilizzare il globbing dei file se scegli tra i file:

select file in *.tar.gz

@Abdull: questo è il comportamento previsto. L'opzione "Esci" esegue un breakche esce dal selectciclo. Puoi aggiungere breakovunque ne hai bisogno. Il manuale di Bash afferma "I comandi vengono eseguiti dopo ogni selezione fino a quando non viene eseguito un comando di interruzione, a quel punto il comando di selezione viene completato."
Dennis Williamson,

FWIW, eseguendolo su 14.04 con GNU bash 4.3.11 produce errori sulla riga 9: ./test.sh: line 9: syntax error near unexpected token "Opzione 1" './test.sh: riga 9: `" Opzione 1 ")' '
Brian Morton

@BrianMorton: non riesco a riprodurlo. È probabile che ti manchi una citazione o qualche altro personaggio prima nella sceneggiatura (o ne abbia uno extra).
Dennis Williamson,

2
@dtmland: PS3è il prompt per il selectcomando. Viene utilizzato automaticamente e non è necessario fare esplicito riferimento. PS3e selectdocumentazione.
Dennis Williamson,

1
@Christian: No, non dovrebbe (ma è possibile se ho usato gli indici $optionsnella casedichiarazione al posto dei valori). Penso che usando i valori sia meglio documentata la funzionalità delle sezioni della casedichiarazione.
Dennis Williamson,

56

Non una nuova risposta in , ma poiché non esiste ancora una risposta accettata, ecco alcuni suggerimenti e trucchi per la codifica, sia per selezionare che per zenità:

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

Vale la pena citare:

  • Entrambi eseguiranno il ciclo fino a quando l'utente non sceglie esplicitamente Esci (o Annulla per zenità). Questo è un buon approccio per i menu di script interattivi: dopo che una scelta è stata selezionata e l'azione è stata eseguita, il menu viene nuovamente presentato per un'altra scelta. Se la scelta è pensata per essere solo una volta, basta usare breakdopo esac(l'approccio zenity potrebbe essere ulteriormente ridotto)

  • Entrambi casesono basati sull'indice, piuttosto che sul valore. Penso che questo sia più facile da programmare e mantenere

  • L'array viene utilizzato anche per l' zenityapproccio.

  • L'opzione "Esci" non è tra le opzioni iniziali e originali. Viene "aggiunto" quando necessario, quindi l'array rimane pulito. Dopotutto, "Esci" non è necessario per Zenity, l'utente può semplicemente fare clic su "Annulla" (o chiudere la finestra) per uscire. Notare come entrambi utilizzano la stessa matrice non trattata di opzioni.

  • PS3e REPLYvars non possono essere rinominati. selectè hardcoded per usare quelli. Tutte le altre variabili nello script (opt, opzioni, prompt, titolo) possono avere qualsiasi nome desiderato, purché tu faccia le modifiche


Spiegazione eccezionale. Grazie. Questa domanda è ancora al primo posto su Google, peccato che sia chiusa.
MountainX,

Si potrebbe utilizzare la stessa casestruttura per la selectversione che si sta utilizzando per la zenityversione: case "$opt" in . . . "${options[0]}" ) . . .(invece di $REPLYe gli indici 1, 2e 3).
Dennis Williamson,

@DennisWilliamson, sì, potrei, e nel codice "reale" sarebbe preferibile utilizzare la stessa struttura in entrambi i casi. Volevo intenzionalmente mostrare la relazione tra $REPLYindici e valori.
MestreLion

55

Utilizzando dialog, il comando sarebbe simile al seguente:

dialog --clear --backtitle "Backtitle here" --title "Title here" --menu "Scegli una delle seguenti opzioni:" 15 40 4 \
1 "Opzione 1" \
2 "Opzione 2" \
3 "Opzione 3"

inserisci qui la descrizione dell'immagine

Mettendolo in uno 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

Mi piacerebbe notare che ho messo la linea TERMINAL=$(tty)nella parte superiore del mio script e poi nella CHOICEdefinizione della variabile ho cambiato 2>&1 >/dev/ttyper 2>&1 >$TERMINALevitare problemi di reindirizzamento se lo script è stato eseguito in un contesto terminale differente.
Shrout1

1
Cosa fa il --backtitleparametro?
TRiG

2
È il titolo dello schermo blu sullo sfondo. Puoi vederlo nell'angolo in alto a sinistra dello screenshot che dice "Backtitle qui".
Alaa Ali,

14

Puoi usare questo semplice script per creare opzioni

#! / Bin / bash
echo "seleziona l'operazione ************"
echo "1) operazione 1"
echo "2) operazione 2"
echo "3) operazione 3"
echo "4) operazione 4" 
leggi n caso $ n in 1) echo "Hai scelto l'opzione 1" ;; 2) echo "Hai scelto l'opzione 2" ;; 3) echo "Hai scelto l'opzione 3" ;; 4) echo "Hai scelto l'opzione 4" ;; *) echo "opzione non valida" ;; esac


8

Ho un'altra opzione che è una miscela di queste risposte, ma ciò che lo rende piacevole è che devi solo premere un tasto e quindi lo script continua grazie -nall'opzione di lettura. In questo esempio, stiamo chiedendo di chiudere, riavviare o semplicemente uscire dallo script usando ANScome nostra variabile e l'utente deve solo premere E, R o S. Ho anche impostato il valore predefinito per uscire, quindi se si preme Invio quindi lo script uscirà.

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

7

Poiché questo è indirizzato a Ubuntu, dovresti usare qualunque backconf sia configurato per l'uso. Puoi scoprire il backend debconf con:

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

Se dice "dialog", allora probabilmente usa whiptailo dialog. Su Lucid è whiptail.

Se fallisce, usa bash "select" come spiegato da Dennis Williamson.


3
Questo è probabilmente eccessivo per quella domanda, ma +1 per menzionare la coda di frusta e il dialogo! Non ero a conoscenza di quei comandi ... molto dolce!
MestreLion,

7
#! / Bin / sh
show_menu () {
    normal = `echo" \ 033 [m "`
    menu = `echo" \ 033 [36m "` #Blu
    numero = `echo" \ 033 [33m "` #yellow
    bgred = `echo" \ 033 [41m "`
    fgred = `echo" \ 033 [31m "`
    printf "\ n $ {menu} ******************************************** *** $ {} normale \ n"
    printf "$ {menu} ** $ {numero} 1) $ {menu} Monta dropbox $ {normale} \ n"
    printf "$ {menu} ** $ {numero} 2) $ {menu} Monta unità USB 500 Gig $ {normale} \ n"
    printf "$ {menu} ** $ {numero} 3) $ {menu} Riavvia Apache $ {normale} \ n"
    printf "$ {menu} ** $ {numero} 4) $ {menu} ssh Server TomCat Frost $ {normale} \ n"
    printf "$ {menu} ** $ {numero} 5) $ {menu} Alcuni altri comandi $ {normale} \ n"
    printf "$ {menu} ********************************************** * $ {} normale \ n"
    printf "Inserisci un'opzione di menu e inserisci o $ {fgred} x per uscire. $ {normal}"
    leggi opt
}

option_picked () {
    msgcolor = `echo" \ 033 [01; 31m "` # grassetto rosso
    normal = `echo" \ 033 [00; 00m "` # bianco normale
    message = $ {@: - "$ {normal} Errore: nessun messaggio passato"}
    printf "$ {msgcolor} $ {message} $ {normal} \ n"
}

chiaro
show_menu
mentre [$ opt! = '']
    fare
    if [$ opt = '']; poi
      Uscita;
    altro
      case $ opt in
        1) cancella;
            option_picked "Opzione 1 selezionata";
            printf "sudo mount / dev / sdh1 / mnt / DropBox /; #The 3 terabyte";
            show_menu;
        ;;
        2) cancella;
            option_picked "Opzione 2 selezionata";
            printf "sudo mount / dev / sdi1 / mnt / usbDrive; #The 500 gig drive";
            show_menu;
        ;;
        3) cancella;
            option_picked "Opzione 3 scelta";
            printf "sudo service apache2 restart";
            show_menu;
        ;;
        4) cancella;
            option_picked "Opzione 4 scelta";
            printf "ssh lmesser @ -p 2010";
            show_menu;
        ;;
        x) di uscita;
        ;;
        \ N) di uscita;
        ;;
        *)chiaro;
            option_picked "Scegli un'opzione dal menu";
            show_menu;
        ;;
      esac
    fi
fatto

2
So che questo è vecchio, ma ha bisogno della prima riga per leggere #! / Bin / bash per compilare.
JClar

Revisione del codice: $manca dalla $optvariabile whilenell'istruzione. La ifdichiarazione è ridondante. Rientro incoerente. L'uso menuin alcuni punti dove dovrebbe essere 'show_menu . show_menu` potrebbe essere messo in cima al ciclo invece di essere ripetuto in ciascuno case. Rientro incoerente. Uso misto di parentesi quadre singole e doppie. Utilizzo di sequenze ANSI codificate invece di tput. Si sconsiglia l'uso di nomi var di maiuscole. FGREDdovrebbe essere chiamato bgred. Uso di backtick anziché $(). La definizione della funzione dovrebbe essere coerente e non usare ...
Dennis Williamson,

... entrambe le forme insieme. I punti e virgola non sono necessari. Alcuni colori, ecc., Definiti due volte. Il caso con \nnon verrà mai eseguito. Forse di più.
Dennis Williamson,

6

Ho usato Zenity, che sembra sempre presente su Ubuntu, funziona molto bene e ha molte capacità. Questo è uno schizzo di un possibile 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

Oops! mi dispiace per la comparsa di questo frammento, la prima volta che pubblica e sembra che debba disattivare l'HTML forse
LazyEchidna,

Meglio, attivato 'esempio di codice' in fase di modifica, mi dispiace per quello
LazyEchidna

5

Menu sfizioso

Provalo prima, quindi visita la mia pagina per una descrizione dettagliata ... Non c'è bisogno di librerie o programmi esterni come dialog o 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

1
Funziona solo con bash, un po 'figo.
Layton Everson,

2
simpatico! "quindi visita la mia pagina per una descrizione dettagliata" c'è un link?
Quercia



Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.