ฉันจะสร้างเมนูเลือกในเชลล์สคริปต์ได้อย่างไร


134

ฉันกำลังสร้างสคริปต์ทุบตีง่าย ๆ และฉันต้องการสร้างเมนูที่เลือกไว้ในนั้นเช่นนี้:

$./script

echo "Choose your option:"

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

และตามทางเลือกของผู้ใช้ฉันต้องการให้มีการดำเนินการที่แตกต่างกัน ฉันเป็นคนเขียนสคริปต์เปลือกหอยทุบตีฉันได้ค้นหาคำตอบบางอย่างในเว็บ แต่ไม่มีอะไรเป็นรูปธรรมจริงๆ


1
คำถามนั้นเก่าและได้รับการป้องกัน แต่ฉันใช้ fzf ลองseq 10 | fzfดู ข้อเสียเปรียบคือ fzf ไม่ได้ติดตั้งโดยค่าเริ่มต้น คุณสามารถหา fzf ได้ที่นี่: github.com/junegunn/fzf
ลินช์

คำตอบ:


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'

แต่คุณไม่สามารถใส่รายการดังกล่าวในตัวแปรสเกลาร์ได้เนื่องจากช่องว่างเป็นหนึ่งในตัวเลือก

คุณยังสามารถใช้ไฟล์ globbing หากคุณเลือกระหว่างไฟล์:

select file in *.tar.gz

@Abdull: นั่นเป็นพฤติกรรมที่ตั้งใจไว้ ตัวเลือก "Quit" จะเรียกใช้งานbreakซึ่งแบ่งออกจากselectลูป คุณสามารถเพิ่มbreakได้ทุกที่ที่คุณต้องการ ทุบตีคู่มือฯ "คำสั่งที่มีการดำเนินการหลังจากการเลือกแต่ละจนกว่าจะมีคำสั่งจะถูกดำเนินการแบ่งจุดที่เลือกคำสั่งเสร็จสมบูรณ์."
Dennis Williamson

FWIW ใช้งาน 14.04 กับ GNU bash 4.3.11 สร้างข้อผิดพลาดในบรรทัดที่ 9: ./test.sh: line 9: syntax error near unexpected token "ตัวเลือก 1" './test.sh: บรรทัด 9: `" ตัวเลือก 1 ")' '
Brian Morton

@BrianMorton: ฉันไม่สามารถทำซ้ำได้ เป็นไปได้ว่าคุณไม่มีเครื่องหมายอัญประกาศหรืออักขระอื่น ๆ ก่อนหน้าในสคริปต์ (หรือมีอักขระเพิ่มเติม)
Dennis Williamson

2
@dtmland: PS3เป็นพรอมต์สำหรับselectคำสั่ง มันถูกใช้โดยอัตโนมัติและไม่จำเป็นต้องอ้างอิงอย่างชัดเจน PS3และselectเอกสารประกอบ
Dennis Williamson

1
@Christian: ไม่มันไม่ควร ( แต่มันจะทำได้ถ้าผมใช้ดัชนีของ$optionsในcaseคำสั่งแทนค่า) ฉันคิดว่าการใช้ค่าที่ดีกว่าเอกสารการทำงานของส่วนของcaseคำสั่ง
Dennis Williamson

56

ไม่ได้เป็นคำตอบใหม่ต่อ seแต่เนื่องจากไม่มีคำตอบที่ได้รับการยอมรับ แต่นี่มีไม่กี่เข้ารหัสเคล็ดลับและเทคนิคสำหรับทั้งเลือกและ 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

มูลค่าการกล่าวขวัญ:

  • ทั้งสองจะวนซ้ำจนกว่าผู้ใช้จะเลือก Quit (หรือยกเลิกเพื่อความเป็นเซน) นี่เป็นวิธีการที่ดีสำหรับเมนูสคริปต์แบบโต้ตอบ: หลังจากเลือกตัวเลือกและดำเนินการแล้วจะมีการนำเสนอเมนูอีกครั้งสำหรับตัวเลือกอื่น หากตัวเลือกนั้นหมายถึงเป็นแบบครั้งเดียวเท่านั้นให้ใช้breakหลังจากesac(วิธีการแบบเซนิตีอาจลดลงได้อีก)

  • ทั้งสองcaseเป็นแบบอิงดัชนีไม่ใช่อิงตามมูลค่า ฉันคิดว่านี่เป็นรหัสที่ง่ายและบำรุงรักษา

  • อาเรย์ยังใช้เป็นzenityแนวทาง

  • ตัวเลือก "Quit" ไม่ได้อยู่ในตัวเลือกเริ่มต้นดั้งเดิม มันคือ "เพิ่ม" เมื่อจำเป็นดังนั้นอาร์เรย์ของคุณจึงสะอาดอยู่เสมอ หลังจากนั้น "Quit" ไม่จำเป็นต้องใช้สำหรับ zenity อยู่แล้วผู้ใช้สามารถคลิก "ยกเลิก" (หรือปิดหน้าต่าง) เพื่อออก ขอให้สังเกตว่าทั้งสองใช้ตัวเลือกอาร์เรย์ที่เหมือนเดิมและไม่ถูกแตะต้องกันอย่างไร

  • PS3และREPLYvars ไม่สามารถเปลี่ยนชื่อได้ selectเป็นฮาร์ดโค้ดที่จะใช้สิ่งเหล่านั้น ตัวแปรอื่น ๆ ทั้งหมดในสคริปต์ (opt, options, prompt, title) สามารถมีชื่อใด ๆ ที่คุณต้องการหากคุณทำการปรับเปลี่ยน


คำอธิบายที่ดีเลิศ ขอขอบคุณ. คำถามนี้ยังคงติดอันดับสูงใน Google ดังนั้นมันจึงปิดเสีย
MountainX

คุณสามารถใช้เดียวกันcaseโครงสร้างสำหรับselectรุ่นที่คุณกำลังใช้สำหรับzenityรุ่น: case "$opt" in . . . "${options[0]}" ) . . .(แทน$REPLYและดัชนี1, 2และ3)
Dennis Williamson

@DennisWilliamson ใช่ฉันทำได้และในรหัส "ของจริง" มันจะดีกว่าที่จะใช้โครงสร้างเดียวกันในทั้งสองกรณี ฉันต้องการแสดงความสัมพันธ์ระหว่าง$REPLYดัชนีและค่านิยมโดยเจตนา
MestreLion

55

ใช้dialogคำสั่งจะมีลักษณะเช่นนี้:

กล่องโต้ตอบ - ชัดเจน - กลับคำบรรยาย "ย้อนกลับคำบรรยายที่นี่" - ชื่อเรื่อง "ที่นี่" - เมนู "เลือกหนึ่งในตัวเลือกต่อไปนี้:" 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/ttyไป2>&1 >$TERMINALเพื่อหลีกเลี่ยงปัญหาการเปลี่ยนเส้นทางถ้าสคริปต์ถูกเรียกใช้ในบริบทขั้วที่แตกต่างกัน
Shrout1

1
อะไร--backtitleพารามิเตอร์ทำอย่างไร
TRiG

2
มันเป็นชื่อของ bluescreen ในพื้นหลัง คุณสามารถดูได้ที่มุมซ้ายบนของภาพหน้าจอที่อ่าน“ Backtitle here”
Alaa Ali

14

คุณสามารถใช้สคริปต์ง่ายๆนี้เพื่อสร้างตัวเลือก

#! / bin / ทุบตี
echo "เลือกการทำงาน ************"
echo "1) การทำงาน 1"
echo "2) การทำงาน 2"
echo "3) การทำงาน 3"
echo "4) การทำงาน 4" 
อ่าน n กรณี $ n 1) echo "คุณเลือกตัวเลือก 1" ;; 2) echo "คุณเลือกตัวเลือก 2"; 3) echo "คุณเลือกตัวเลือก 3" ;; 4) echo "คุณเลือกตัวเลือก 4" ;; *) echo "ตัวเลือกที่ไม่ถูกต้อง" ;; esac


8

ฉันมีตัวเลือกอีกหนึ่งตัวที่ผสมคำตอบเหล่านี้ แต่สิ่งที่ทำให้ดีคือคุณต้องกดปุ่มเดียวแล้วสคริปต์ก็ดำเนินต่อไปด้วย-nตัวเลือกการอ่าน ในตัวอย่างนี้เราได้รับการพร้อมท์ให้ปิดรีบูตหรือออกจากสคริปต์โดยใช้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"

หากจะกล่าวว่า "ไดอะล็อก" แล้วมันมีแนวโน้มการใช้งานหรือwhiptail บนสุวิมลมันdialogwhiptail

หากล้มเหลวให้ใช้ bash "select" ตามที่อธิบายโดย Dennis Williamson


3
นี่อาจเป็นคำถามมากเกินไปสำหรับคำถามนั้น แต่ +1 สำหรับพูดถึงแส้และบทสนทนา! ฉันไม่ทราบคำสั่งเหล่านั้น ... น่ารักมาก!
MestreLion

7
#! / bin / ดวลจุดโทษ
show_menu () {
    Normal = `echo" \ 033 [m "`
    menu = `echo" \ 033 [36m "` #Blue
    number = `echo" \ 033 [33m "` #yellow
    bgred = `echo" \ 033 [41m "`
    fgred = `echo" \ 033 [31m "`
    printf "\ n $ {เมนู} ****************************************** *** $ {ปกติ} \ n"
    printf "$ {menu} ** $ {number} 1) $ {menu} เมานต์ดรอปบ็อกซ์ $ {ปกติ} \ n"
    printf "$ {menu} ** $ {number} 2) $ {menu} Mount USB 500 Gig Drive ไดรฟ์ $ {ปกติ} \ n"
    printf "$ {menu} ** $ {number} 3) $ {menu} รีสตาร์ท Apache $ {normal} \ n"
    printf "$ {menu} ** $ {number} 4) $ {menu} ssh Frost TomCat Server $ {ปกติ} \ n"
    printf "$ {menu} ** $ {number} 5) $ {menu} คำสั่งอื่น ๆ $ {normal} \ n"
    printf "$ {เมนู} ******************************************** * $ {ปกติ} \ n"
    printf "โปรดป้อนตัวเลือกเมนูและป้อนหรือ $ {fgred} x เพื่อออก $ {ปกติ}"
    อ่าน opt
}

option_picked () {
    msgcolor = `echo" \ 033 [01; 31m "` # ตัวหนาสีแดง
    ปกติ = `echo" \ 033 [00; 00m "` # ขาวปกติ
    message = $ {@: - "ข้อผิดพลาด $ $ ปกติ: ไม่มีข้อความผ่าน"}
    printf "$ {msgcolor} $ {message} $ {normal} \ n"
}

ชัดเจน
show_menu
ในขณะที่ [$ opt! = '']
    ทำ
    หาก [$ opt = '']; แล้วก็
      ทางออก;
    อื่น
      กรณีที่ $ opt
        1) ชัดเจน
            option_picked "เลือก 1 ตัวเลือก";
            printf "sudo mount / dev / sdh1 / mnt / DropBox /; #The 3 เทราไบต์";
            show_menu;
        ;;
        2) ชัดเจน;
            option_picked "เลือก 2 ตัวเลือก";
            printf "sudo mount / dev / sdi1 / mnt / usbDrive; # ไดรฟ์ 500 กิกะไบต์";
            show_menu;
        ;;
        3) ชัดเจน;
            option_picked "เลือก 3 ตัวเลือก";
            printf "sudo service apache2 restart";
            show_menu;
        ;;
        4) ชัดเจน;
            option_picked "เลือก 4 ตัวเลือก";
            printf "ssh lmesser @ -p 2010";
            show_menu;
        ;;
        x) ทางออก;
        ;;
        \ n) ทางออก;
        ;;
        *)ชัดเจน;
            option_picked "เลือกตัวเลือกจากเมนู";
            show_menu;
        ;;
      esac
    Fi
เสร็จแล้ว

2
ฉันรู้ว่ามันเก่า แต่ต้องการบรรทัดแรกเพื่ออ่าน #! / bin / bash เพื่อคอมไพล์
JClar

การตรวจสอบโค้ด: ตัวแปร$นั้นหายไปจาก$optตัวแปรในwhileคำสั่ง ifงบซ้ำซ้อน การเยื้องที่ไม่สอดคล้องกัน ใช้menuในบางสถานที่ที่ควรจะเป็น 'show_menu . show_menu` สามารถใส่ที่ด้านบนของลูปแทนที่จะถูกทำซ้ำในแต่ละcaseสถานที่ การเยื้องที่ไม่สอดคล้องกัน การผสมการใช้วงเล็บเหลี่ยมเดี่ยวและสองเท่า การใช้ลำดับ ANSI แบบtputตายตัวแทนการใช้ ไม่แนะนำให้ใช้ชื่อ all-caps var ควรจะเรียกว่าFGRED bgredใช้ backticks $()แทน คำจำกัดความของฟังก์ชั่นควรจะสอดคล้องกันและไม่ควรใช้ ...
Dennis Williamson

... ทั้งสองฟอร์มเข้าด้วยกัน ไม่จำเป็นต้องใช้เครื่องหมายอัฒภาค บางสี ฯลฯ กำหนดสองครั้ง กรณีที่มี\nจะไม่ถูกดำเนินการ อาจจะมากกว่านั้น
Dennis Williamson

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

ดีกว่าเปิด 'ตัวอย่างรหัส' ในการแก้ไขขออภัยเกี่ยวกับเรื่องนี้
LazyEchidna

5

ทุบตีเมนูแฟนซี

ลองใช้ก่อนจากนั้นไปที่หน้าเว็บของฉันเพื่อดูคำอธิบายโดยละเอียด ... ไม่จำเป็นต้องใช้ไลบรารีหรือโปรแกรมภายนอกเช่นกล่องโต้ตอบหรือความเป็นเซน ...

#/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
ใช้ได้กับทุบตีเท่านั้น
Layton Everson

2
ดี! "จากนั้นไปที่หน้าของฉันสำหรับคำอธิบายโดยละเอียด" มีลิงก์หรือไม่
โอ๊ก


2

มีคำถามเดียวกันในserverfault ที่ตอบแล้ว วิธีการแก้ปัญหามีใช้whiptail


ขอบคุณ แต่เนื่องจากสคริปต์ของฉันมีไว้เพื่อการบริโภคเป็นหลักฉันไม่ต้องการให้มีการพึ่งพาเพิ่มเติมอีก แต่ฉันจะบุ๊คมาร์คไว้สำหรับใช้ในอนาคตใครจะรู้
Daniel Rodrigues

-1

สมมติว่าคุณต้องการที่จะใช้เมนูเชลล์สคริปต์ธรรมดา (ไม่มี UI แฟนซี) ตรวจสอบตัวอย่างเมนูจากhttp://www.tldp.org/LDP/abs/html/testbranch.html


1
เช่นเดียวกับตัวอย่างอื่น ๆ ส่วนใหญ่ในคู่มือสคริปต์ทุบตีขั้นสูงตัวอย่างเหล่านั้นมีข้อบกพร่องและการปฏิบัติที่ไม่ดี
geirha
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.