Bagaimana saya bisa membuat menu pilih dalam skrip shell?


134

Saya membuat skrip bash sederhana dan saya ingin membuat menu pilih di dalamnya, seperti ini:

$./script

echo "Choose your option:"

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

Dan sesuai pilihan pengguna, saya ingin berbagai tindakan dijalankan. Saya noob scripting bash shell, saya telah mencari di web untuk beberapa jawaban, tetapi tidak ada yang benar-benar konkret.


1
Pertanyaannya sudah tua dan terlindungi, tetapi saya menggunakan fzf. Coba seq 10 | fzf. Kekurangannya adalah bahwa fzf tidak diinstal secara default. Anda dapat menemukan fzf di sini: github.com/junegunn/fzf
Lynch

Jawaban:


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

Tambahkan breakpernyataan di mana pun Anda membutuhkan selectloop untuk keluar. Jika a breaktidak dilakukan, selectstatemen akan berulang dan menu ditampilkan kembali.

Dalam opsi ketiga, saya menyertakan variabel yang ditetapkan oleh selectpernyataan untuk menunjukkan bahwa Anda memiliki akses ke nilai-nilai itu. Jika Anda memilihnya, itu akan menampilkan:

you chose choice 3 which is Option 3

Anda bisa melihat yang $REPLYberisi string yang Anda masukkan saat diminta. Ini digunakan sebagai indeks ke dalam array ${options[@]}seolah-olah array berdasarkan 1. Variabel $optberisi string dari indeks itu dalam array.

Perhatikan bahwa pilihannya bisa berupa daftar sederhana langsung dalam selectpernyataan seperti ini:

select opt in foo bar baz 'multi word choice'

tetapi Anda tidak dapat menempatkan daftar seperti itu dalam variabel skalar karena spasi di salah satu pilihan.

Anda juga dapat menggunakan penggumpalan file jika Anda memilih di antara file-file:

select file in *.tar.gz

@ Abdull: Itulah perilaku yang dimaksud. Opsi "Quit" mengeksekusi breakyang keluar dari selectloop. Anda dapat menambahkan di breakmana saja Anda membutuhkannya. The Bash pengguna negara "Perintah-perintah yang dieksekusi setelah setiap seleksi sampai perintah istirahat dijalankan, di mana titik pilih perintah selesai."
Dennis Williamson

FWIW, menjalankan ini pada 14,04 dengan GNU bash 4.3.11 menghasilkan kesalahan pada baris 9: ./test.sh: line 9: syntax error near unexpected token "Opsi 1" './test.sh: baris 9: `" Opsi 1 ")'`
Brian Morton

@BrianMorton: Saya tidak bisa mereproduksi itu. Kemungkinan Anda melewatkan kutipan atau karakter lain sebelumnya dalam skrip (atau memiliki tambahan).
Dennis Williamson

2
@ dtmland: PS3adalah prompt untuk selectperintah. Ini digunakan secara otomatis dan tidak perlu direferensikan secara eksplisit. PS3dan selectdokumentasi.
Dennis Williamson

1
@Christian: Tidak, seharusnya tidak (tetapi bisa jika saya menggunakan indeks $optionsdalam casepernyataan bukan nilai-nilai). Saya pikir menggunakan nilai-nilai yang lebih baik mendokumentasikan fungsi bagian casepernyataan itu.
Dennis Williamson

56

Bukan jawaban yang baru per se , tetapi karena tidak ada jawaban yang diterima belum, berikut adalah beberapa coding tips dan trik, untuk kedua pilih dan 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

Layak disebut:

  • Keduanya akan berulang hingga pengguna secara eksplisit memilih Berhenti (atau Batal untuk zenity). Ini adalah pendekatan yang baik untuk menu skrip interaktif: setelah pilihan dipilih dan tindakan dilakukan, menu disajikan lagi untuk pilihan lain. Jika pilihan hanya dimaksudkan satu kali saja, gunakan saja breaksetelahnya esac(pendekatan zenity dapat dikurangi lebih lanjut juga)

  • Keduanya caseberbasis indeks, bukan berbasis nilai. Saya pikir ini lebih mudah untuk kode dan pemeliharaan

  • Array juga digunakan untuk zenitypendekatan.

  • Opsi "Berhenti" tidak termasuk di antara opsi awal dan asli. Ini "ditambahkan" saat dibutuhkan, sehingga array Anda tetap bersih. Lagipula, "Berhenti" tidak diperlukan untuk zenity, pengguna cukup mengklik "Batal" (atau tutup jendela) untuk keluar. Perhatikan bagaimana keduanya menggunakan array opsi yang sama dan tidak tersentuh.

  • PS3dan REPLYvars tidak dapat diganti namanya. selecthardcoded untuk menggunakan itu. Semua variabel lain dalam skrip (opt, opsi, prompt, judul) dapat memiliki nama apa pun yang Anda inginkan, asalkan Anda melakukan penyesuaian


Penjelasan luar biasa. Terima kasih. Pertanyaan ini masih berperingkat tinggi di Google, jadi sayang sekali ditutup.
MountainX

Anda bisa menggunakan yang sama casestruktur untuk selectversi yang Anda gunakan untuk zenityversi: case "$opt" in . . . "${options[0]}" ) . . .(bukan $REPLYdan indeks 1, 2dan 3).
Dennis Williamson

@ DennisWilliamson, ya saya bisa, dan dalam kode "nyata" akan lebih baik untuk menggunakan struktur yang sama dalam kedua kasus. Saya sengaja ingin menunjukkan hubungan antara $REPLY, indeks dan nilai.
MestreLion

55

Menggunakan dialog, perintahnya akan terlihat seperti ini:

dialog --clear --backtitle "Backtitle di sini" --title "Judul di sini" --menu "Pilih salah satu dari opsi berikut:" 15 40 4 \
1 "Opsi 1" \
2 "Opsi 2" \
3 "Opsi 3"

masukkan deskripsi gambar di sini

Menempatkannya dalam skrip:

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

Saya ingin untuk dicatat bahwa saya menempatkan garis TERMINAL=$(tty)di bagian atas naskah saya dan kemudian di CHOICEdefinisi variabel saya berubah 2>&1 >/dev/ttyuntuk 2>&1 >$TERMINALmenghindari masalah pengalihan jika skrip dijalankan dalam konteks terminal yang berbeda.
Shrout1

1
Apa yang dilakukan --backtitleparameter?
TRiG

2
Ini adalah judul layar biru di latar belakang. Anda dapat melihatnya di sudut kiri atas tangkapan layar yang bertuliskan "Backtitle di sini".
Alaa Ali

14

Anda dapat menggunakan skrip sederhana ini untuk membuat opsi

#! / bin / bash
gema "pilih operasi ************"
gema "1) operasi 1"
gema "2) operasi 2"
gema "3) operasi 3"
gema "4) operasi 4" 
baca n case $ n in 1) gema "Anda memilih Opsi 1";; 2) gema "Anda memilih Opsi 2";; 3) gema "Anda memilih Opsi 3";; 4) gema "Anda memilih Opsi 4" ;; *) echo "opsi tidak valid" ;; esac


8

Saya punya satu opsi lagi yang merupakan campuran dari jawaban-jawaban ini, tetapi yang membuatnya menyenangkan adalah Anda hanya perlu menekan satu tombol dan kemudian skrip berlanjut berkat -nopsi baca. Dalam contoh ini, kami diminta untuk mematikan, reboot, atau cukup keluar dari skrip menggunakan ANSsebagai variabel kami dan pengguna hanya perlu menekan E, R, atau S. Saya juga mengatur default untuk keluar sehingga jika masuk ditekan maka skrip akan keluar.

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

Karena ini ditargetkan pada Ubuntu, Anda harus menggunakan backend debconf apa pun yang dikonfigurasi untuk digunakan. Anda dapat menemukan backend debconf dengan:

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

Jika dikatakan "dialog" maka kemungkinan menggunakan whiptailatau dialog. Di Lucid itu whiptail.

Jika gagal, gunakan bash "select" seperti yang dijelaskan oleh Dennis Williamson.


3
Ini mungkin berlebihan untuk pertanyaan itu, tetapi +1 untuk menyebutkan whiptail dan dialog! Saya tidak menyadari perintah-perintah itu ... sangat manis!
MestreLion

7
#! / bin / sh
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 $ {menu} **************************************** *** $ {normal} \ n "
    printf "$ {menu} ** $ {number} 1) $ {menu} Mount dropbox $ {normal} \ n"
    printf "$ {menu} ** $ {number} 2) $ {menu} Pasang USB 500 Gig Drive $ {normal} \ n"
    printf "$ {menu} ** $ {number} 3) $ {menu} Restart Apache $ {normal} \ n"
    printf "$ {menu} ** $ {number} 4) $ {menu} ssh Frost TomCat Server $ {normal} \ n"
    printf "$ {menu} ** $ {number} 5) $ {menu} Beberapa perintah lain $ {normal} \ n"
    printf "$ {menu} ****************************************** * $ {normal} \ n "
    printf "Silakan masukkan opsi menu dan masukkan atau $ {fgred} x untuk keluar. $ {normal}"
    baca opt
}

option_picked () {
    msgcolor = `echo" \ 033 [01; 31m "` # tebal merah
    normal = `echo" \ 033 [00; 00m "` # normal putih
    message = $ {@: - "$ {normal} Kesalahan: Tidak ada pesan yang lewat"}
    printf "$ {msgcolor} $ {message} $ {normal} \ n"
}

bersih
show_menu
sementara [$ opt! = '']
    melakukan
    jika [$ opt = '']; kemudian
      keluar;
    lain
      case $ opt in
        1) jelas;
            option_picked "Option 1 Picked";
            printf "sudo mount / dev / sdh1 / mnt / DropBox /; #The 3 terabyte";
            show_menu;
        ;;
        2) jelas;
            option_picked "Option 2 Picked";
            printf "sudo mount / dev / sdi1 / mnt / usbDrive; #The 500 gig drive";
            show_menu;
        ;;
        3) jelas;
            option_picked "Option 3 Picked";
            printf "sudo service apache2 restart";
            show_menu;
        ;;
        4) jelas;
            option_picked "Option 4 Picked";
            printf "ssh lmesser @ -p 2010";
            show_menu;
        ;;
        x) keluar;
        ;;
        \ n) keluar;
        ;;
        *)bersih;
            option_picked "Pilih opsi dari menu";
            show_menu;
        ;;
      esac
    fi
Selesai

2
Saya tahu ini sudah tua, tetapi perlu baris pertama untuk membaca #! / Bin / bash untuk dikompilasi.
JClar

Ulasan kode: Tidak $ada dari $optvariabel dalam whilepernyataan. The ifpernyataan berlebihan. Lekukan yang tidak konsisten. Menggunakan menudi beberapa tempat di mana seharusnya 'show_menu . show_menu` dapat diletakkan di atas loop daripada diulang di masing-masing case. Lekukan yang tidak konsisten. Mencampur penggunaan kurung kotak tunggal dan yang dua kali lipat. Menggunakan urutan ANSI yang dikodekan keras alih-alih tput. Penggunaan nama all-caps var tidak disarankan. FGREDharus dipanggil bgred. Gunakan backticks sebagai ganti $(). Definisi fungsi harus konsisten dan tidak menggunakan ...
Dennis Williamson

... kedua bentuk bersama. Titik koma terminal tidak diperlukan. Beberapa warna, dll., Didefinisikan dua kali. Kasus dengan \ntidak akan pernah dieksekusi. Mungkin lebih.
Dennis Williamson

6

Saya telah menggunakan Zenity, yang sepertinya selalu ada di Ubuntu, berfungsi dengan sangat baik dan memiliki banyak kemampuan. Ini adalah sketsa dari menu yang memungkinkan:

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

Ups! maaf tentang penampilan cuplikan ini, pertama kali memposting dan sepertinya harus mematikan HTML
LazyEchidna

Lebih baik, mengaktifkan 'contoh kode' dalam pengeditan, maaf tentang itu
LazyEchidna

5

Bash menu mewah

Cobalah dulu, lalu kunjungi halaman saya untuk deskripsi terperinci ... Tidak perlu perpustakaan atau program eksternal seperti dialog atau 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
Ini hanya bekerja dengan bash, agak keren.
Layton Everson

2
bagus! "lalu kunjungi halaman saya untuk deskripsi terperinci" apakah ada tautan?
oak


2

Sudah ada pertanyaan yang sama di serverfault yang dijawab. Solusi di sana menggunakan whiptail .


Terima kasih, tetapi karena skrip saya adalah untuk konsumsi umum, saya tidak ingin ada ketergantungan tambahan. Tapi saya akan menandai itu untuk digunakan di masa depan, siapa tahu.
Daniel Rodrigues

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.