Como posso criar um menu de seleção em um script de shell?

134

Estou criando um script bash simples e quero criar um menu de seleção, como este:

$./script

echo "Choose your option:"

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

E de acordo com a escolha do usuário, quero que diferentes ações sejam executadas. Eu sou um noob de script do shell bash, procurei na web por algumas respostas, mas não tenho nada realmente concreto.

Daniel Rodrigues
fonte
1
A questão é antiga e protegida, mas eu uso o fzf. Tente seq 10 | fzf. A desvantagem é que o fzf não está instalado por padrão. Você pode encontrar o fzf aqui: github.com/junegunn/fzf
Lynch

Respostas:

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

Adicione breakinstruções sempre que precisar do selectloop para sair. Se a breaknão for executada, a selectinstrução será repetida e o menu será exibido novamente.

Na terceira opção, incluí variáveis ​​definidas pela selectinstrução para demonstrar que você tem acesso a esses valores. Se você escolher, ele produzirá:

you chose choice 3 which is Option 3

Você pode ver que $REPLYcontém a sequência digitada no prompt. É usado como um índice na matriz ${options[@]}como se a matriz fosse baseada em 1. A variável $optcontém a sequência desse índice na matriz.

Observe que as opções podem ser uma lista simples diretamente na selectdeclaração como esta:

select opt in foo bar baz 'multi word choice'

mas você não pode colocar essa lista em uma variável escalar por causa dos espaços em uma das opções.

Você também pode usar o globbing de arquivos se escolher entre os arquivos:

select file in *.tar.gz
Dennis Williamson
fonte
@ Abdull: Esse é o comportamento pretendido. A opção "Quit" executa um breakque interrompe o selectloop. Você pode adicionar em breakqualquer lugar que precisar. O manual do Bash declara "Os comandos são executados após cada seleção até que um comando de interrupção seja executado, momento em que o comando de seleção é concluído".
Dennis Williamson
FWIW, executando isso no 14.04 com GNU bash 4.3.11 produz erros na linha 9: ./test.sh: line 9: syntax error near unexpected token "Opção 1" './test.sh: linha 9: `" Opção 1 ")'`
Brian Morton
@BrianMorton: Não consigo reproduzir isso. É provável que você esteja faltando uma citação ou algum outro personagem no início do script (ou tenha um extra).
Dennis Williamson
2
@dtmland: PS3é o prompt do selectcomando. É usado automaticamente e não precisa ser referenciado explicitamente. PS3e selectdocumentação.
Dennis Williamson
1
@Christian: Não, não deveria (mas poderia se eu usasse os índices de $optionsna casedeclaração em vez dos valores). Eu acho que usar os valores documenta melhor a funcionalidade das seções da casedeclaração.
Dennis Williamson
56

Não é uma resposta nova por si só , mas como ainda não há resposta aceita, aqui estão algumas dicas e truques de codificação, tanto para a seleção quanto para a zenidade:

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 a pena mencionar:

  • Ambos serão repetidos até que o usuário escolha explicitamente Sair (ou Cancelar para zenidade). Essa é uma boa abordagem para menus de script interativos: depois que uma opção é selecionada e a ação é executada, o menu é apresentado novamente para outra escolha. Se a escolha for feita apenas uma vez, basta usar breakdepois esac(a abordagem de zenidade também pode ser reduzida)

  • Ambos casesão baseados em índices e não em valores. Eu acho que isso é mais fácil de codificar e manter

  • Matriz também é usada para zenityabordagem.

  • A opção "Sair" não está entre as opções originais iniciais. Ele é "adicionado" quando necessário, para que sua matriz fique limpa. Afinal, "Sair" não é necessário para o zenity, o usuário pode simplesmente clicar em "Cancelar" (ou fechar a janela) para sair. Observe como os dois usam a mesma matriz intocada de opções.

  • PS3e REPLYvars não podem ser renomeados. selectestá codificado para usá-los. Todas as outras variáveis ​​no script (opt, opções, prompt, título) podem ter os nomes desejados, desde que você faça os ajustes

MestreLion
fonte
Explicação impressionante. Obrigado. Esta questão ainda está no topo do Google, por isso está fechada.
MountainX
Você pode usar a mesma caseestrutura para a selectversão que você está usando para a zenityversão: case "$opt" in . . . "${options[0]}" ) . . .(em vez de $REPLYe os índices 1, 2e 3).
Dennis Williamson
@DennisWilliamson, sim, eu poderia, e no código "real" seria preferível usar a mesma estrutura nos dois casos. Eu intencionalmente queria mostrar a relação entre $REPLYíndices e valores.
MestreLion 31/05
55

Usando dialog, o comando ficaria assim:

dialog --clear --backtitle "Backtitle here" --title "Title here" --menu "Escolha uma das seguintes opções:" 15 40 4 \
1 "Opção 1" \
2 "Opção 2" \
3 "Opção 3"

insira a descrição da imagem aqui

Colocando em um 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
fonte
Eu gostaria de notar que eu colocar a linha TERMINAL=$(tty)na parte superior do meu script e, em seguida, na CHOICEdefinição da variável mudei 2>&1 >/dev/ttypara 2>&1 >$TERMINALevitar problemas de redirecionamento se o script foi executado em um contexto terminal diferente.
Shrout1
1
O que o --backtitleparâmetro faz?
Trig
2
É o título da tela azul em segundo plano. Você pode vê-lo no canto superior esquerdo da captura de tela que diz "Backtitle here".
Alaa Ali
14

Você pode usar este script simples para criar opções

#! / bin / bash
eco "selecione a operação ************"
eco "1) operação 1"
eco "2) operação 2"
eco "3) operação 3"
eco "4) operação 4" 
leia n case $ n in 1) eco "Você escolheu a opção 1" ;; 2) echo "Você escolheu a opção 2" ;; 3) eco "Você escolheu a opção 3" ;; 4) eco "Você escolheu a opção 4" ;; *) echo "opção inválida" ;; esac

jibin
fonte
8

Eu tenho mais uma opção que é uma mistura dessas respostas, mas o que torna interessante é que você só precisa pressionar uma tecla e o script continua graças à -nopção de leitura. Neste exemplo, estamos solicitando o desligamento, a reinicialização ou simplesmente a saída do script usando ANScomo variável e o usuário precisa apenas pressionar E, R ou S. Também defino o padrão para sair, portanto, se enter for pressionado, o script vai sair.

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

Como isso é direcionado ao Ubuntu, você deve usar o back-end que o debconf estiver configurado para usar. Você pode descobrir o back-end do debconf com:

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

Se aparecer "diálogo", provavelmente usa whiptailou dialog. Em Lucid é whiptail.

Se isso falhar, use o bash "select", conforme explicado por Dennis Williamson.

Li Lo
fonte
3
Provavelmente é um exagero para essa pergunta, mas +1 por mencionar chicote e diálogo! Eu não estava ciente desses comandos ... muito doce!
MestreLion 5/08/11
7
#! / bin / sh
show_menu () {
    normal = `eco" \ 033 [m "`
    menu = `eco" \ 033 [36m "` # Azul
    number = `eco" \ 033 [33m "` # amarelo
    bgred = `eco" \ 033 [41m "`
    fgred = `eco" \ 033 [31m "`
    printf "\ n $ {menu} ****************************************** *** $ {normal} \ n "
    printf "$ {menu} ** $ {number} 1) $ {menu} Montar caixa de depósito $ {normal} \ n"
    printf "$ {menu} ** $ {number} 2) $ {menu} monte a unidade USB 500 Gig $ {normal} \ n"
    printf "$ {menu} ** $ {number} 3) $ {menu} Reinicie o Apache $ {normal} \ n"
    printf "$ {menu} ** $ {number} 4) $ {menu} ssh Servidor Frost TomCat $ {normal} \ n"
    printf "$ {menu} ** $ {number} 5) $ {menu} Alguns outros comandos $ {normal} \ n"
    printf "$ {menu} ********************************************** * $ {normal} \ n "
    printf "Digite uma opção de menu e digite ou $ {fgred} x para sair. $ {normal}"
    leia opt
}

option_picked () {
    msgcolor = `echo" \ 033 [01; 31m "` # vermelho negrito
    normal = `eco" \ 033 [00; 00m "` # branco normal
    message = $ {@: - "$ {normal} Erro: Nenhuma mensagem foi passada"}
    printf "$ {msgcolor} $ {message} $ {normal} \ n"
}

Claro
show_menu
enquanto [$ opt! = '']
    Faz
    if [$ opt = '']; então
      Saída;
    outro
      case $ opt in
        1) claro;
            option_picked "Opção 1 escolhida";
            printf "sudo mount / dev / sdh1 / mnt / DropBox /; #Os 3 terabytes";
            show_menu;
        ;;
        2) claro;
            option_picked "Opção 2 escolhida";
            printf "sudo mount / dev / sdi1 / mnt / usbDrive; #A unidade de 500 GB";
            show_menu;
        ;;
        3) claro;
            option_picked "Opção 3 escolhida";
            printf "serviço sudo apache2 restart";
            show_menu;
        ;;
        4) claro;
            option_picked "Opção 4 escolhida";
            printf "ssh lmesser @ -p 2010";
            show_menu;
        ;;
        x) sair;
        ;;
        \ n) sair;
        ;;
        *)Claro;
            option_picked "Escolha uma opção no menu";
            show_menu;
        ;;
      esac
    fi
feito
Alex Lucard
fonte
2
Eu sei que isso é antigo, mas precisa da primeira linha para ler #! / Bin / bash para compilar.
JClar
Revisão de código: $está faltando a $optvariável na whileinstrução. A ifdeclaração é redundante. Recuo inconsistente. Usar menuem alguns lugares onde deveria estar 'show_menu . show_menu` pode ser colocado no topo do loop em vez de ser repetido em cada um case. Recuo inconsistente. Uso misturado de colchetes simples e duplos. Usando sequências ANSI codificadas em vez de tput. O uso de nomes var all-caps não é recomendado. FGREDdeve ser chamado bgred. Uso de backticks em vez de $(). Definição de função deve ser consistente e não use ...
Dennis Williamson
... ambas as formas juntas. Ponto e vírgula terminal não são necessários. Algumas cores etc. são definidas duas vezes. O caso com \nnunca será executado. Talvez mais.
Dennis Williamson
6

Eu usei o Zenity, que sempre aparece no Ubuntu, funciona muito bem e tem muitos recursos. Este é um esboço de um menu possível:

#! /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
fonte
Opa! muito sobre o aparecimento deste trecho, primeira postagem tempo e parece tem que virar HTML off talvez
LazyEchidna
Melhor, ativado 'amostra de código' na edição, desculpe por isso
LazyEchidna 5/05
5

Bash fancy menu

Experimente primeiro e depois visite minha página para obter uma descrição detalhada ... Não há necessidade de bibliotecas ou programas externos como diálogo ou zenidade ...

#/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
fonte
1
Isso só funciona com o bash, meio legal.
Layton Everson
2
legais! "então visite minha página para descrição detalhada" existe link?
carvalho
2

Já existe a mesma pergunta em serverfault respondida. A solução lá usa chicote .

txwikinger
fonte
Obrigado, mas como o meu script é para consumo convencional, não quero que ele tenha nenhuma dependência adicional. Mas vou marcar isso para uso futuro, quem sabe.
Daniel Rodrigues
-1

Supondo que você queira usar um menu de script de shell simples (sem interface de usuário sofisticada), verifique o exemplo de menu em http://www.tldp.org/LDP/abs/html/testbranch.html .

João Pinto
fonte
1
Como em muitos outros trechos do guia avançado de scripts de bash, esses trechos contêm bugs e práticas inadequadas.
Geirha #