Làm cách nào để tạo menu chọn trong tập lệnh shell?


134

Tôi đang tạo một tập lệnh bash đơn giản và tôi muốn tạo một menu chọn trong đó, như thế này:

$./script

echo "Choose your option:"

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

Và theo lựa chọn của người dùng, tôi muốn các hành động khác nhau được thực thi. Tôi là một bash shell script noob, tôi đã tìm kiếm trên mạng một số câu trả lời, nhưng không có gì thực sự cụ thể.


1
Câu hỏi đã cũ và được bảo vệ, nhưng tôi sử dụng fzf. Hãy thử seq 10 | fzf. Hạn chế là fzf không được cài đặt theo mặc định. Bạn có thể tìm thấy fzf tại đây: github.com/junegunn/fzf
Lynch

Câu trả lời:


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

Thêm breakcâu lệnh bất cứ nơi nào bạn cần selectvòng lặp để thoát. Nếu a breakkhông được thực hiện, các selectvòng lặp câu lệnh và menu sẽ được hiển thị lại.

Trong tùy chọn thứ ba, tôi đã bao gồm các biến được đặt bởi selectcâu lệnh để chứng minh rằng bạn có quyền truy cập vào các giá trị đó. Nếu bạn chọn nó, nó sẽ xuất ra:

you chose choice 3 which is Option 3

Bạn có thể thấy $REPLYcó chứa chuỗi bạn đã nhập tại dấu nhắc. Nó được sử dụng như một chỉ mục vào mảng ${options[@]}như thể mảng dựa trên 1. Biến $optchứa chuỗi từ chỉ mục đó trong mảng.

Lưu ý rằng các lựa chọn có thể là một danh sách đơn giản trực tiếp trong selectcâu lệnh như sau:

select opt in foo bar baz 'multi word choice'

nhưng bạn không thể đặt một danh sách như vậy trong một biến vô hướng vì các khoảng trắng trong một trong các lựa chọn.

Bạn cũng có thể sử dụng tệp toàn cầu nếu bạn chọn trong số các tệp:

select file in *.tar.gz

@Abdull: Đó là hành vi dự định. Tùy chọn "Thoát" thực thi một breakcái mà thoát ra khỏi selectvòng lặp. Bạn có thể thêm breakbất cứ nơi nào bạn cần nó. Các nhãn hiệu Bash bang "Các lệnh được thực hiện sau mỗi lựa chọn cho đến khi một lệnh break được thực thi, lúc này lệnh select hoàn tất."
Dennis Williamson

FWIW, chạy cái này vào ngày 14.04 với GNU bash 4.3.11 tạo ra lỗi trên dòng 9: ./test.sh: line 9: syntax error near unexpected token "Tùy chọn 1" './test.sh: dòng 9: `" Tùy chọn 1 ")'`
Brian Morton

@BrianMorton: Tôi không thể sao chép điều đó. Có khả năng bạn đang thiếu một trích dẫn hoặc một số ký tự khác trước đó trong kịch bản (hoặc có thêm).
Dennis Williamson

2
@dtmland: PS3là lời nhắc cho selectlệnh. Nó được sử dụng tự động và không cần phải được tham chiếu rõ ràng. PS3selecttài liệu.
Dennis Williamson

1
@Christian: Không, không nên (nhưng có thể nếu tôi sử dụng các chỉ số $optionstrong casecâu lệnh thay vì các giá trị). Tôi nghĩ rằng việc sử dụng các giá trị tài liệu tốt hơn chức năng của các phần của casetuyên bố.
Dennis Williamson

56

Không phải là một câu trả lời mới cho mỗi gia nhập , nhưng vì không có câu trả lời chấp nhận nào, dưới đây là một vài mẹo và thủ thuật mã hóa, cho cả hai lựa chọn và 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

Đáng nói:

  • Cả hai sẽ lặp cho đến khi người dùng chọn rõ ràng Thoát (hoặc Hủy vì sự thoải mái). Đây là một cách tiếp cận tốt cho các menu script tương tác: sau khi lựa chọn được chọn và thực hiện hành động, menu sẽ được trình bày lại cho lựa chọn khác. Nếu sự lựa chọn chỉ là một lần duy nhất, chỉ cần sử dụng breaksau esac(phương pháp tiếp cận có thể được giảm thêm)

  • Cả hai caseđều dựa trên chỉ mục, thay vì dựa trên giá trị. Tôi nghĩ rằng điều này là dễ dàng hơn để mã hóa và duy trì

  • Mảng cũng được sử dụng để zenitytiếp cận.

  • Tùy chọn "Thoát" không nằm trong số các tùy chọn ban đầu, ban đầu. Nó được "thêm" khi cần, vì vậy mảng của bạn sạch sẽ. Dù sao đi nữa, "Thoát" không cần thiết cho sự thoải mái, người dùng chỉ cần nhấp vào "Hủy" (hoặc đóng cửa sổ) để thoát. Lưu ý cách cả hai sử dụng cùng một mảng tùy chọn.

  • PS3REPLYvars không thể được đổi tên. selectđược mã hóa cứng để sử dụng chúng. Tất cả các biến khác trong tập lệnh (opt, tùy chọn, dấu nhắc, tiêu đề) có thể có bất kỳ tên nào bạn muốn, miễn là bạn thực hiện các điều chỉnh


Giải thích tuyệt vời. Cảm ơn bạn. Câu hỏi này vẫn được xếp hạng cao trên Google, vì vậy quá tệ, nó đã bị đóng.
MountainX

Bạn có thể sử dụng cùng một casecấu trúc cho các selectphiên bản mà bạn đang sử dụng cho các zenityphiên bản: case "$opt" in . . . "${options[0]}" ) . . .(thay vì $REPLYvà các chỉ số 1, 23).
Dennis Williamson

@DennisWilliamson, vâng tôi có thể, và trong mã "thực", nên sử dụng cùng một cấu trúc trong cả hai trường hợp. Tôi cố ý muốn hiển thị mối quan hệ giữa $REPLY, chỉ mục và giá trị.
MestreLion

55

Sử dụng dialog, lệnh sẽ trông như thế này:

hộp thoại --clear --backtitle "Backtitle here" --title "Title here" --menu "Chọn một trong các tùy chọn sau:" 15 40 4 \
1 "Tùy chọn 1" \
2 "Tùy chọn 2" \
3 "Lựa chọn 3"

nhập mô tả hình ảnh ở đây

Đưa nó vào một kịch bản:

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

Tôi muốn lưu ý rằng tôi đặt dòng TERMINAL=$(tty)ở phía trên cùng của kịch bản của tôi và sau đó trong CHOICEđịnh nghĩa biến tôi đã thay đổi 2>&1 >/dev/ttyđể 2>&1 >$TERMINALtránh các vấn đề chuyển hướng nếu kịch bản được chạy trong một bối cảnh thiết bị đầu cuối khác nhau.
Shrout1

1
Không những gì --backtitlethông số làm gì?
TRiG

2
Đó là tiêu đề của màn hình xanh trong nền. Bạn có thể thấy nó ở góc trên cùng bên trái của ảnh chụp màn hình có dòng chữ Backtitle tại đây.
Alaa Ali

14

Bạn có thể sử dụng tập lệnh đơn giản này để tạo tùy chọn

#! / bin / bash
tiếng vang "chọn thao tác ******."
tiếng vang "1) hoạt động 1"
tiếng vang "2) hoạt động 2"
tiếng vang "3) hoạt động 3"
tiếng vang "4) hoạt động 4" 
đọc n trường hợp $ n trong 1) echo "Bạn đã chọn Tùy chọn 1" ;; 2) echo "Bạn đã chọn Tùy chọn 2" ;; 3) echo "Bạn đã chọn Tùy chọn 3" ;; 4) echo "Bạn đã chọn Tùy chọn 4" ;; *) echo "tùy chọn không hợp lệ" ;; esac


8

Tôi có thêm một lựa chọn là hỗn hợp của những câu trả lời này, nhưng điều tuyệt vời là bạn chỉ cần nhấn một phím và sau đó tập lệnh tiếp tục nhờ vào -ntùy chọn đọc. Trong ví dụ này, chúng tôi đang nhắc tắt máy, khởi động lại hoặc đơn giản là thoát tập lệnh bằng cách sử dụng ANSbiến của chúng tôi và người dùng chỉ phải nhấn E, R hoặc S. Tôi cũng đặt mặc định để thoát vì vậy nếu nhấn enter thì tập lệnh sẽ thoát ra

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

Vì mục tiêu này được nhắm mục tiêu tại Ubuntu, bạn nên sử dụng bất kỳ nội dung phụ trợ nào được cấu hình để sử dụng. Bạn có thể tìm hiểu phần phụ trợ gỡ lỗi với:

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

Nếu nó nói "hộp thoại" thì có khả năng nó sử dụng whiptailhoặc dialog. Trên Lucid đó whiptail.

Nếu thất bại, hãy sử dụng bash "select" như được giải thích bởi Dennis Williamson.


3
Đây có lẽ là quá mức cần thiết cho câu hỏi đó, nhưng +1 để đề cập đến whiptail và hộp thoại! Tôi đã không nhận thức được những lệnh đó ... rất ngọt ngào!
MestreLion

7
#! / thùng / sh
Hiển thị Menu(){
    bình thường = `echo" \ 033 [m "`
    menu = `echo" \ 033 [36m "` #Blue
    số = `echo" \ 033 [33m "` #yellow
    bgred = `tiếng vang" \ 033 [41m "`
    fgred = `echo" \ 033 [31m "`
    printf "\ n $ {menu} ****** / TÌM KIẾM *** $ {bình thường} \ n "
    printf "$ {menu} ** $ {số} 1) $ {menu} Mount dropbox $ {normal} \ n"
    printf "$ {menu} ** $ {số} 2) $ {menu} Gắn USB 500 Gig Drive $ {normal} \ n"
    printf "$ {menu} ** $ {số} 3) $ {menu} Khởi động lại Apache $ {normal} \ n"
    printf "$ {menu} ** $ {số} 4) $ {menu} ssh Máy chủ Frost TomCat $ {bình thường} \ n"
    printf "$ {menu} ** $ {number} 5) $ {menu} Một số lệnh khác $ {normal} \ n"
    printf "$ {menu} ****** / 4/8/8/8/8/8/8/8/8/8/8/8/8/8/8/8/8/8/8/8/8/8/8/8 * $ {bình thường} \ n "
    printf "Vui lòng nhập tùy chọn menu và nhập hoặc $ {fgred} x để thoát. $ {normal}"
    đọc opt
}

tùy chọn_picky () {
    dircolor = `echo" \ 033 [01; 31m "` # đậm màu đỏ
    normal = `echo" \ 033 [00; 00m "` # màu trắng bình thường
    message = $ {@: - "$ {normal} Lỗi: Không có tin nhắn nào được chuyển"}
    printf "$ {dircolor} $ {message} $ {normal} \ n"
}

thông thoáng
Hiển thị Menu
trong khi [$ opt! = '']
    làm
    if [$ opt = '']; sau đó
      lối ra;
    khác
      trường hợp $ opt in
        1) rõ ràng;
            tùy chọn_picky "Tùy chọn 1 được chọn";
            printf "sudo mount / dev / sdh1 / mnt / DropBox /; #The 3 terabyte";
            Hiển thị Menu;
        ;;
        2) rõ ràng;
            tùy chọn_picky "Tùy chọn 2 được chọn";
            printf "sudo mount / dev / sdi1 / mnt / usbDrive; #The 500 gig drive";
            Hiển thị Menu;
        ;;
        3) rõ ràng;
            tùy chọn_picky "Tùy chọn 3 được chọn";
            printf "sudo dịch vụ apache2 khởi động lại";
            Hiển thị Menu;
        ;;
        4) rõ ràng;
            tùy chọn_picky "Tùy chọn 4 được chọn";
            printf "ssh lmesser @ -p 2010";
            Hiển thị Menu;
        ;;
        x) thoát;
        ;;
        \ n) thoát;
        ;;
        *)thông thoáng;
            tùy chọn_picky "Chọn một tùy chọn từ menu";
            Hiển thị Menu;
        ;;
      esac
    fi
làm xong

2
Tôi biết điều này đã cũ, nhưng cần dòng đầu tiên để đọc #! / Bin / bash để biên dịch.
JClar

Xem lại mã: $Thiếu từ $optbiến trong whilecâu lệnh. Các iftuyên bố là dư thừa. Lõm không nhất quán. Sử dụng menuở một số nơi nên là 'show_menu . show_menu` có thể được đặt ở đầu vòng lặp thay vì được lặp lại ở mỗi vòng case. Lõm không nhất quán. Trộn sử dụng dấu ngoặc đơn và nhân đôi. Sử dụng các chuỗi ANSI được mã hóa cứng thay vì tput. Không nên sử dụng tên var all-caps. FGREDnên được gọi bgred. Sử dụng backticks thay vì $(). Định nghĩa chức năng phải nhất quán và không sử dụng ...
Dennis Williamson

... cả hai hình thức cùng nhau. Dấu chấm phẩy đầu cuối không cần thiết. Một số màu, vv, được xác định hai lần. Các trường hợp với \nsẽ không bao giờ được thực hiện. Có lẽ nhiều hơn.
Dennis Williamson

6

Tôi đã sử dụng Zenity, dường như luôn có trong Ubuntu, hoạt động rất tốt và có nhiều khả năng. Đây là một bản phác thảo của một menu có thể:

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

Úi! xin lỗi về sự xuất hiện của đoạn trích này, lần đầu tiên đăng bài và dường như phải tắt HTML có lẽ
LazyEchidna

Tốt hơn, bật 'mẫu mã' trong chỉnh sửa, xin lỗi về điều đó
LazyEchidna

5

Thực đơn ưa thích của Bash

Hãy dùng thử trước, sau đó truy cập trang của tôi để biết mô tả chi tiết ... Không cần các thư viện hoặc chương trình bên ngoài như hộp thoại hoặc tiện nghi ...

#/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
Điều này chỉ hoạt động với bash, kinda mát mẻ.
Layton Everson

2
đẹp! "Sau đó truy cập trang của tôi để mô tả chi tiết" có liên kết không?
gỗ sồi


2

Đã có câu hỏi tương tự trong serverfault được trả lời. Các giải pháp có sử dụng whiptail .


Cảm ơn, nhưng vì kịch bản của tôi là dành cho tiêu dùng chính, tôi không muốn nó có thêm bất kỳ sự phụ thuộc nào. Nhưng tôi sẽ đánh dấu trang đó để sử dụng trong tương lai, ai biết được.
Daniel Coleues

-1

Giả sử bạn muốn sử dụng menu kịch bản lệnh shell đơn giản (không có giao diện người dùng ưa thích), hãy kiểm tra ví dụ trình đơn từ http://www.tldp.org/LDP/abs/html/testbranch.html .


1
Cũng như hầu hết các đoạn mã khác trong hướng dẫn bash-scripting nâng cao, những đoạn mã đó chứa lỗi và thực tiễn xấu.
geirha
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.