如何在Shell脚本中创建选择菜单?


134

我正在创建一个简单的bash脚本,并且想要在其中创建一个选择菜单,如下所示:

$./script

echo "Choose your option:"

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

根据用户的选择,我希望执行不同的操作。我是一个bash shell脚本编写新手,我在网上搜索了一些答案,但并没有真正具体的内容。


1
这个问题是古老且受保护的,但我使用fzf。尝试seq 10 | fzf。缺点是默认情况下未安装fzf。您可以在这里找到fzf:github.com/junegunn/fzf
Lynch,

Answers:


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

break在需要select循环退出的任何地方添加语句。如果break未执行a,则该select语句循环并重新显示菜单。

在第三个选项中,我包含了由select语句设置的变量,以证明您可以访问这些值。如果选择它,它将输出:

you chose choice 3 which is Option 3

您可以看到其中$REPLY包含您在提示符下输入的字符串。它用作数组的索引,${options[@]}就好像该数组基于1。该变量$opt包含来自数组中该索引的字符串。

请注意,选择可能是直接在select语句中的简单列表,如下所示:

select opt in foo bar baz 'multi word choice'

但是您不能将这样的列表放入标量变量中,因为其中一个选项存在空格。

如果要在文件中进行选择,还可以使用文件遍历:

select file in *.tar.gz

@Abdull:这是预期的行为。“ Quit”选项执行一个break跳出select循环的“ a”。您可以在break任何需要的地方添加。该击手册状态“的命令的每一个的选择,直到执行中断命令,在该点处选择命令完成之后,执行”。
丹尼斯·威廉姆森

FWIW,使用GNU bash 4.3.11在14.04上运行时,在第9行:./test.sh: line 9: syntax error near unexpected token “ Option 1”'./test.sh:第9行:`“ Option 1”)'`
Brian Morton,

@BrianMorton:我无法复制。您可能在脚本的前面缺少引号或其他字符(或有多余的字符)。
丹尼斯·威廉姆森

2
@dtmland:PS3select命令提示符。它是自动使用的,不需要显式引用。PS3select文档。
丹尼斯·威廉姆森

1
@Christian:不,它不应该(但它可能如果我使用的索引$optionscase陈述,而不是值)。我认为使用这些值可以更好地说明该case语句各节的功能。
丹尼斯·威廉姆森

56

本身并不是一个新的答案,但是由于尚无公认的答案,因此这里有一些针对select和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

值得一提:

  • 两者都会循环播放,直到用户明确选择“退出”(或出于“取消”而取消)。这是用于交互式脚本菜单的好方法:选择一个选项并执行操作后,再次显示菜单以供另一个选择。如果选择仅是一次性的,则仅break在之后使用esac(也可以进一步减少禅宗方法)

  • 两者case都是基于索引的,而不是基于价值的。我认为这更容易编码和维护

  • 数组也用于zenity方法。

  • “退出”选项不在初始的原始选项中。需要时将其“添加”,以使您的阵列保持干净。毕竟,“ zenit”并不需要“退出”,用户只需单击“取消”(或关闭窗口)即可退出。注意两者如何使用相同的未更改的选项数组。

  • PS3并且REPLYvars 无法重命名。select被硬编码为使用它们。脚本中的所有其他变量(选项,选项,提示,标题)可以具有您想要的任何名称,只要您进行调整


很棒的解释。谢谢。这个问题在Google上仍然排名很高,所以很遗憾它已经关闭。
MountainX

你可以使用相同的case结构,为select您使用的版本zenity版本:case "$opt" in . . . "${options[0]}" ) . . .(而不是$REPLY和索引123)。
丹尼斯·威廉姆森

@DennisWilliamson,是的,我可以,在“真实”代码中,最好在两种情况下都使用相同的结构。我故意要显示$REPLY,索引和值之间的关系。
MestreLion

55

使用dialog,命令如下所示:

对话框--clear --backtitle“此处的标题” --title“此处的标题” --menu“选择以下选项之一:” 15 40 4 \
1“选项1”
2“选项2”
3“选项3”

在此处输入图片说明

将其放入脚本中:

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

我想指出的是,我将该行TERMINAL=$(tty)放在脚本的顶部,然后在CHOICE变量定义中进行了更改2>&1 >/dev/tty2>&1 >$TERMINAL以避免在脚本在其他终端上下文中运行时发生重定向问题。
Shrout18年

1
什么是--backtitle参数呢?
TRiG '18

2
它是背景中蓝屏的标题。您可以在屏幕截图的左上角看到它,显示为“ Backtitle here”。
Alaa Ali

14

您可以使用此简单脚本创建选项

#!/ bin / bash
回显“选择操作************”
回声“ 1)操作1”
回声“ 2)操作2”
回声“ 3)操作3”
回声“ 4)操作4” 
读n 案件$ n 1)回显“您选择了选项1”; 2)回显“您选择了选项2”; 3)回显“您选择了选项3”; 4)回显“您选择了选项4”; *)回显“无效选项”; 埃萨克


8

我还有一个其他选项,这些选项混合了这些答案,但是很不错的一点是,您只需要按一个键,然后由于-nread选项,脚本就可以继续。在此示例中,我们提示关闭,重新启动或仅使用ANS用作变量退出脚本,而用户仅需按E,R或S。我还将默认值设置为退出,因此如果按Enter,则脚本将退出。

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

由于这是针对Ubuntu的,因此您应该使用配置为使用的任何后端debconf。您可以通过以下方式找到debconf后端:

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

如果显示“对话”,则可能使用whiptaildialog。在Lucid上是whiptail

如果失败,请按照Dennis Williamson的说明使用bash“选择”。


3
对于这个问题,这可能是多余的,但是提及鞭尾和对话则为+1!我没有意识到那些命令...非常贴心!
MestreLion,2011年

7
#!/ bin / sh
show_menu(){
    normal =`echo“ \ 033 [m”`
    menu =`echo“ \ 033 [36m”`#蓝色
    number =`echo“ \ 033 [33m”`#yellow
    bgred =`echo“ \ 033 [41m”`“
    fgred ='echo“ \ 033 [31m”`
    printf“ \ n $ {menu} ********************************************** *** $ {普通} \ n“
    printf“ $ {menu} ** $ {number} 1)$ {menu}挂载保管箱$ {normal} \ n”
    printf“ $ {menu} ** $ {number} 2)$ {menu}安装USB 500 Gig驱动器$ {normal} \ n”
    printf“ $ {menu} ** $ {number} 3)$ {menu}重新启动Apache $ {normal} \ n”
    printf“ $ {menu} ** $ {number} 4)$ {menu} ssh Frost TomCat服务器$ {normal} \ n”
    printf“ $ {menu} ** $ {number} 5)$ {menu}其他一些命令$ {normal} \ n”
    printf“ $ {menu} ************************************************ * $ {normal} \ n“
    “ printf”请输入菜单选项,然后输入或$ {fgred} x退出。$ {normal}“
    阅读选项
}

option_picked(){
    msgcolor =`echo“ \ 033 [01; 31m”`#粗体红色
    normal =`echo“ \ 033 [00; 00m”`#正常白色
    message = $ {@:-“ $ {normal}错误:未传递任何消息”}
    printf“ $ {msgcolor} $ {message} $ {normal} \ n”
}

明确
show_menu
而[$ opt!='']
    做
    如果[$ opt ='']; 然后
      出口;
    其他
      案例$ opt in
        1)清除;
            option_picked“选择了选项1”;
            printf“ sudo mount / dev / sdh1 / mnt / DropBox /;#3 TB”
            show_menu;
        ;;
        2)清除;
            option_picked“选择了选项2”;
            printf“ sudo mount / dev / sdi1 / mnt / usbDrive;#500 gig驱动器”;
            show_menu;
        ;;
        3)清除;
            option_picked“选择了选项3”;
            printf“ sudo服务apache2重新启动”;
            show_menu;
        ;;
        4)清除;
            option_picked“选择了选项4”;
            printf“ ssh lmesser @ -p 2010”;
            show_menu;
        ;;
        x)退出;
        ;;
        \ n)退出;
        ;;
        *)明确;
            option_picked“从菜单中选择一个选项”;
            show_menu;
        ;;
      埃萨克
    科幻
做完了

2
我知道这很旧,但是需要第一行读取#!/ bin / bash才能进行编译。
JClar 2014年

代码查看:语句中$$opt变量缺少while。该if语句是多余的。缩进不一致。menu在某些应该使用show_menu . show_menu`的地方使用,可以放在循环的顶部,而不必在每个循环中重复case。缩进不一致。单方括号和双方括号混合使用。使用硬编码的ANSI序列代替tput。不建议使用全大写var名​​称。FGRED应该叫bgred。使用反引号代替$()。函数定义应该一致并且不要使用...
Dennis Williamson

两者一起形成 不需要终端分号。一些颜色等定义了两次。的情况\n将永远不会执行。也许更多。
丹尼斯·威廉姆森

6

我使用过Zenity,它在Ubuntu中似乎总是存在,工作得很好并且具有许多功能。这是可能的菜单的示意图:

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

糟糕!抱歉,此代码段的出现是首次发布,似乎不得不关闭HTML了
LazyEchidna 2011年

更好的是,在编辑中启用了“代码示例”,对此感到遗憾
LazyEchidna 2011年

5

重击花式菜单

首先尝试一下,然后访问我的页面以获取详细描述...不需要外部库或对话框或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
这仅适用于bash,有点酷。
雷顿·埃弗森

2
真好!“然后访问我的页面以获取详细描述”是否存在链接?
橡木



By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.