¿Cómo puedo crear un menú de selección en un script de shell?


134

Estoy creando un script bash simple y quiero crear un menú de selección en él, como este:

$./script

echo "Choose your option:"

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

Y según la elección del usuario, quiero que se ejecuten diferentes acciones. Soy un novato de scripting de bash shell, busqué en la web algunas respuestas, pero no obtuve nada realmente concreto.


1
La pregunta es antigua y protegida, pero uso fzf. Tratar seq 10 | fzf. El inconveniente es que fzf no está instalado por defecto. Puede encontrar fzf aquí: github.com/junegunn/fzf
Lynch

Respuestas:


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

Agregue breakdeclaraciones donde necesite selectque salga el bucle. Si breakno se realiza a, la selectinstrucción se repite y el menú se vuelve a mostrar.

En la tercera opción, incluí variables establecidas por la selectdeclaración para demostrar que tiene acceso a esos valores. Si lo elige, generará:

you chose choice 3 which is Option 3

Puede ver que $REPLYcontiene la cadena que ingresó en la solicitud. Se utiliza como índice en la matriz ${options[@]}como si la matriz estuviera basada en 1. La variable $optcontiene la cadena de ese índice en la matriz.

Tenga en cuenta que las opciones podrían ser una lista simple directamente en la selectdeclaración como esta:

select opt in foo bar baz 'multi word choice'

pero no puede poner dicha lista en una variable escalar debido a los espacios en una de las opciones.

También puede usar el bloqueo de archivos si elige entre archivos:

select file in *.tar.gz

@Abdull: Ese es el comportamiento previsto. La opción "Salir" ejecuta un breakque sale del selectbucle. Puede agregarlo breakdonde lo necesite. El manual de Bash dice "Los comandos se ejecutan después de cada selección hasta que se ejecuta un comando de interrupción, momento en el que se completa el comando de selección".
Dennis Williamson

FWIW, ejecutar esto en 14.04 con GNU bash 4.3.11 produce errores en la línea 9: ./test.sh: line 9: syntax error near unexpected token "Opción 1" './test.sh: línea 9: `" Opción 1 ")' '
Brian Morton

@BrianMorton: No puedo reproducir eso. Es probable que te falte una cita o algún otro personaje antes en el guión (o tengas un extra).
Dennis Williamson

2
@dtmland: PS3es la solicitud del selectcomando. Se usa automáticamente y no necesita ser referenciado explícitamente. PS3y selectdocumentación.
Dennis Williamson el

1
@Christian: No, no debería (pero podría hacerlo si usara los índices de $optionsen la casedeclaración en lugar de los valores). Creo que usar los valores documenta mejor la funcionalidad de las secciones de la casedeclaración.
Dennis Williamson

56

No es una respuesta nueva per se , pero como todavía no hay una respuesta aceptada, aquí hay algunos consejos y trucos de codificación, tanto para seleccionar como para 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

Vale la pena mencionar:

  • Ambos se repetirán hasta que el usuario elija explícitamente Salir (o Cancelar por zenity). Este es un buen enfoque para los menús de guiones interactivos: después de seleccionar una opción y realizar una acción, el menú se presenta nuevamente para otra opción. Si la elección está destinada a ser de una sola vez, solo use breakdespués esac(el enfoque zenity también podría reducirse aún más)

  • Ambos caseestán basados ​​en índices, más que en valores. Creo que esto es más fácil de codificar y mantener

  • La matriz también se usa para el zenityenfoque.

  • La opción "Salir" no se encuentra entre las opciones iniciales originales. Se "agrega" cuando es necesario, para que su matriz se mantenga limpia. Después de todo, "Salir" no es necesario para zenity de todos modos, el usuario puede simplemente hacer clic en "Cancelar" (o cerrar la ventana) para salir. Observe cómo ambos usan la misma matriz de opciones intacta.

  • PS3y REPLYvars no pueden ser renombrados. selectestá codificado para usar esos. Todas las demás variables en el script (opt, options, prompt, title) pueden tener los nombres que desee, siempre que realice los ajustes


Impresionante explicación. Gracias. Esta pregunta todavía ocupa un lugar destacado en Google, por lo que es una pena que esté cerrada.
MountainX

Se podría utilizar la misma caseestructura para la selectversión que está utilizando para la zenityversión: case "$opt" in . . . "${options[0]}" ) . . .(en lugar de $REPLYy los índices 1, 2y 3).
Dennis Williamson

@DennisWilliamson, sí podría, y en código "real" sería preferible usar la misma estructura en ambos casos. Intencionalmente quería mostrar la relación entre $REPLY, índices y valores.
MestreLion

55

Usando dialog, el comando se vería así:

cuadro de diálogo --clear --backtitle "Backtitle here" --title "Title here" --menu "Elija una de las siguientes opciones:" 15 40 4 \
1 "Opción 1" \
2 "Opción 2" \
3 "Opción 3"

ingrese la descripción de la imagen aquí

Poniéndolo en 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

Me gustaría señalar que puse la línea TERMINAL=$(tty)en la parte superior de mi guión y luego en la CHOICEdefinición de la variable cambié 2>&1 >/dev/ttya 2>&1 >$TERMINALevitar problemas de redirección si el script se ejecuta en un contexto terminal diferente.
Shrout1

1
¿Qué hace el --backtitleparámetro?
TRiG

2
Es el título de la pantalla azul en el fondo. Puede verlo en la esquina superior izquierda de la captura de pantalla que dice "Backtitle here".
Alaa Ali el

14

Puede usar este script simple para crear opciones

#! / bin / bash
echo "seleccione la operación ************"
echo "1) operación 1"
echo "2) operación 2"
echo "3) operación 3"
echo "4) operación 4" 
leer n caso $ n en 1) echo "Usted eligió la Opción 1"; 2) echo "Usted eligió la Opción 2"; 3) echo "Usted eligió la Opción 3"; 4) echo "Usted eligió la Opción 4"; *) echo "opción no válida"; esac


8

Tengo una opción más que es una mezcla de estas respuestas, pero lo que lo hace agradable es que solo necesita presionar una tecla y luego el script continúa gracias a la -nopción de lectura. En este ejemplo, le pedimos que apague, reinicie o simplemente salga del script usando ANSnuestra variable y el usuario solo tiene que presionar E, R o S. También configuro el valor predeterminado para salir, de modo que si se presiona Enter, el script saldrá

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

Dado que esto está dirigido a Ubuntu, debe usar cualquier debconf backend configurado para usar. Puede encontrar el backend de debconf con:

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

Si dice "diálogo", entonces probablemente use whiptailo dialog. En Lucid es whiptail.

Si eso falla, use bash "select" como lo explicó Dennis Williamson.


3
Probablemente sea una exageración para esa pregunta, ¡pero +1 por mencionar whiptail y dialog! No estaba al tanto de esos comandos ... ¡muy dulce!
MestreLion

7
#! / bin / sh
Muestrame el menu(){
    normal = `echo" \ 033 [m "`
    menu = `echo" \ 033 [36m "` #Azul
    número = `echo" \ 033 [33m "` #amarillo
    bgred = `echo" \ 033 [41m "`
    fgred = `echo" \ 033 [31m "`
    printf "\ n $ {menu} ****************************************** *** $ {normal} \ n "
    printf "$ {menu} ** $ {number} 1) $ {menu} Montar dropbox $ {normal} \ n"
    printf "$ {menu} ** $ {number} 2) $ {menu} Montar USB 500 Gig Drive $ {normal} \ n"
    printf "$ {menu} ** $ {number} 3) $ {menu} Reiniciar Apache $ {normal} \ n"
    printf "$ {menu} ** $ {number} 4) $ {menu} ssh Servidor Frost TomCat $ {normal} \ n"
    printf "$ {menu} ** $ {number} 5) $ {menu} Algunos otros comandos $ {normal} \ n"
    printf "$ {menu} ******************************************** * $ {normal} \ n "
    printf "Ingrese una opción de menú e ingrese o $ {fgred} x para salir. $ {normal}"
    leer opt
}

option_picked () {
    msgcolor = `echo" \ 033 [01; 31m "` # negrita rojo
    normal = `echo" \ 033 [00; 00m "` # blanco normal
    mensaje = $ {@: - "$ {normal} Error: no se pasó ningún mensaje"}
    printf "$ {msgcolor} $ {message} $ {normal} \ n"
}

claro
Muestrame el menu
mientras que [$ opt! = '']
    hacer
    si [$ opt = '']; entonces
      salida;
    más
      caso $ opt in
        1) claro;
            option_picked "Opción 1 elegida";
            printf "sudo mount / dev / sdh1 / mnt / DropBox /; #The 3 terabyte";
            Muestrame el menu;
        ;;
        2) claro;
            option_picked "Opción 2 elegida";
            printf "sudo mount / dev / sdi1 / mnt / usbDrive; #The 500 gig drive";
            Muestrame el menu;
        ;;
        3) claro;
            option_picked "Opción 3 elegida";
            printf "sudo service apache2 restart";
            Muestrame el menu;
        ;;
        4) claro;
            option_picked "Opción 4 elegida";
            printf "ssh lmesser @ -p 2010";
            Muestrame el menu;
        ;;
        x) salir;
        ;;
        \ n) salir;
        ;;
        *)claro;
            option_picked "Elige una opción del menú";
            Muestrame el menu;
        ;;
      esac
    fi
hecho

2
Sé que esto es viejo, pero necesita la primera línea para leer #! / Bin / bash para compilar.
JClar

Revisión de código: $falta la $optvariable en la whiledeclaración. La ifdeclaración es redundante. Sangría inconsistente. El uso menuen algunos lugares donde debería ser 'show_menu . show_menu` podría colocarse en la parte superior del bucle en lugar de repetirse en cada uno case. Sangría inconsistente. Uso combinado de corchetes simples y dobles. Usar secuencias ANSI codificadas en lugar de tput. No se recomienda el uso de nombres var en mayúsculas. FGREDdebería llamarse bgred. Uso de backticks en lugar de $(). La definición de la función debe ser coherente y no usar ...
Dennis Williamson

... ambas formas juntas. No se necesitan puntos y comas terminales. Algunos colores, etc., se definen dos veces. El caso con \nnunca se ejecutará. Quizás más.
Dennis Williamson

6

He usado Zenity, que siempre parece estar en Ubuntu, funciona muy bien y tiene muchas capacidades. Este es un boceto de un posible menú:

#! /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

¡Uy! lo de la aparición de este fragmento, primer destino de tiempo y parece que volverse HTML fuera quizá
LazyEchidna

Mejor, activé la 'muestra de código' en la edición, perdón por eso
LazyEchidna 05 de

5

Menú sofisticado de Bash

Pruébelo primero, luego visite mi página para obtener una descripción detallada ... No necesita bibliotecas o programas externos como diálogo 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
Esto solo funciona con bash, algo genial.
Layton Everson

2
¡bonito! "luego visite mi página para obtener una descripción detallada" ¿hay un enlace?
roble


2

Ya hay la misma pregunta en serverfault respondida. La solución allí usa whiptail .


Gracias, pero como mi script es para consumo general, no quiero que tenga ninguna dependencia adicional. Pero lo marcaré para usarlo en el futuro, quién sabe.
Daniel Rodrigues

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.