Bir kabuk betiğinde nasıl seçim menüsü oluşturabilirim?


134

Basit bir bash betiği oluşturuyorum ve bunun gibi bir seçim menüsü oluşturmak istiyorum:

$./script

echo "Choose your option:"

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

Ve kullanıcının seçimine göre, farklı eylemlerin gerçekleştirilmesini istiyorum. Ben bir bash shell script noob, ben bazı cevaplar için web aradım ama gerçekten somut bir şeyim yok.


1
Soru eski ve korumalı, ancak fzf kullanıyorum. Dene seq 10 | fzf. Dezavantajı, fzf'nin varsayılan olarak yüklenmemiş olmasıdır. Fzf'yi burada bulabilirsiniz: github.com/junegunn/fzf
Lynch

Yanıtlar:


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

Çıkmak breakiçin selectdöngünün gerektiği yere ifadeleri ekleyin . A breakgerçekleştirilmezse, selectifade döngüler ve menü yeniden görüntülenir.

Üçüncü seçenekte, selectbu değerlere erişiminiz olduğunu göstermek için ifade tarafından ayarlanan değişkenleri dahil ettim . Seçerseniz, çıktı olacaktır:

you chose choice 3 which is Option 3

Bilgi $REPLYisteminde girdiğiniz dizgiyi içerdiğini görebilirsiniz . ${options[@]}Dizi 1 tabanlıymış gibi dizinin içine bir dizin olarak kullanılır . Değişken $opt, dizideki o dizinden gelen dizeyi içerir.

Seçimlerin şöyle bir selectifadede doğrudan basit bir liste olabileceğini unutmayın :

select opt in foo bar baz 'multi word choice'

ancak, seçimlerden birindeki boşluklar nedeniyle böyle bir listeyi skalar değişkeni içine koyamazsınız.

Dosyalar arasında seçim yapıyorsanız globbing dosyasını da kullanabilirsiniz:

select file in *.tar.gz

@Abdull: Amaçlanan davranış budur. "Çık" seçeneği döngüden breakkopan bir komut çalıştırır select. İhtiyacınız olan breakher yere ekleyebilirsiniz . Bash manuel devletler "komutları bir ara komut hangi seçme komutu sonlandırılıncaya, idam edilene kadar her seçimden sonra yürütülür."
Dennis Williamson,

: FWIW, GNU bash 4.3.11 ile 14.04 bu çalışan hat 9 hataları üretir ./test.sh: line 9: syntax error near unexpected token "Seçenek 1" './test.sh: hat 9: ` "Seçenek 1")'`
Brian Morton

@ BrianMorton: Bunu çoğaltamıyorum. Senaryoda daha önce bir alıntı veya başka bir karakter eksik (veya fazladan olabilir).
Dennis Williamson,

2
@dtmland: PS3için istemi olan selectkomuta. Otomatik olarak kullanılır ve açıkça belirtilmesi gerekmez. PS3ve selectbelgeler.
Dennis Williamson

1
@Christian: Hayır, olmamalı (ama olabilir ben endeksleri kullanılırsa $optionsiçinde caseyerine değerlerin ifadesi). Bence değerleri daha iyi kullanmak caseifadenin bölümlerinin işlevselliğini belgeliyor .
Dennis Williamson,

56

Kendi başına yeni bir cevap değil , fakat henüz kabul edilmiş bir cevap olmadığı için, hem seçkinlik hem de seçkinlik için birkaç kodlama ipucu ve püf noktası:

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

Bahsetmeye değer:

  • Her ikisi de kullanıcı açıkça Quit (veya zenity için Cancel) seçimini yapana kadar döngülenecektir. Bu, etkileşimli script menüleri için iyi bir yaklaşımdır: bir seçim seçilip işlem yapıldıktan sonra, menü başka bir seçim için tekrar sunulur. Seçim sadece bir kereye mahsustursa , sadece kullandıktan breaksonra kullanın esac(zenity yaklaşımı daha da azaltılabilir)

  • Her ikisi casede değer temelli değil, dizin temellidir. Bunun kodlanması ve bakımı daha kolay olduğunu düşünüyorum

  • Dizi zenityyaklaşımı için de kullanılır .

  • "Çık" seçeneği, başlangıçtaki orijinal seçenekler arasında değildir. Gerektiğinde "eklenir", böylece diziniz temiz kalır. Bundan sonra, yine de zenit için "Çık" gerekli değildir, kullanıcı çıkmak için sadece "İptal" (veya pencereyi kapat) tıklayabilirsiniz. Her ikisinin de aynı, el değmemiş seçenek dizisini nasıl kullandığına dikkat edin.

  • PS3ve REPLYdeğişkenler olabilir değil adlandırılacak. selectbunları kullanmak için kodlanmış. Komut dosyasındaki diğer tüm değişkenler (opt, options, prompt, title), ayarları yapmanız koşuluyla istediğiniz adlara sahip olabilir.


Müthiş bir açıklama. Teşekkür ederim. Bu soru hala Google’da en üst sıralarda yer aldığından çok kapalı.
MountainX

Aynı kullanabilirsiniz caseiçin yapıyı selectiçin kullandığınız sürümü zenity: version case "$opt" in . . . "${options[0]}" ) . . .(yerine $REPLYve endeksleri 1, 2ve 3).
Dennis Williamson,

@DennisWilliamson, evet yapabilirim ve "gerçek" kodda her iki durumda da aynı yapıyı kullanmak tercih edilir. Kasıtlı olarak $REPLYindeksler ve değerler arasındaki ilişkiyi göstermek istedim .
MestreLion

55

Kullanarak dialog, komut şöyle görünür:

dialog --clear --backtitle "Buraya Backtitle" --title "Başlık buraya" --menu "Aşağıdaki seçeneklerden birini seçin:" 15 40 4 \
1 "Seçenek 1" \
2 "Seçenek 2" \
3 "Seçenek 3"

görüntü tanımını buraya girin

Bir komut dosyasına koyarak:

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

Ben çizgi koymak dikkat etmek istiyorum TERMINAL=$(tty)Senaryomun üstündeki ve sonra CHOICEdeğişken tanımı Değiştim 2>&1 >/dev/ttyiçin 2>&1 >$TERMINALkomut farklı bir uç bağlamında çalıştırılmış yönlendirme sorunlarını önlemek için.
Shrout1

1
--backtitleParametre ne yapar ?
TRiG

2
Arka plandaki mavi ekranın adı. Ekranın sol üst köşesinde “Backtitle here” yazan metni görebilirsiniz.
Alaa Ali,

14

Seçenekler oluşturmak için bu basit betiği kullanabilirsiniz

#! / Bin / bash
echo "operasyonu seç ************"
yankı "1) işlem 1"
yankı "2) işlem 2"
yankı "3) işlem 3"
yankı "4) işlem 4" 
n oku $ n durumunda 1) yankı "Sen Seçenek 1'i seçtin"; 2) yankı "Siz Seçenek 2'yi seçtiniz"; 3) "Seçenek 3'ü seçtiniz" yankısı; 4) yankı "Sen Seçenek 4'ü seçtin"; *) echo "geçersiz seçenek" ;; esac


8

Bu cevapların bir karışımı olan bir seçeneğim daha var, ancak bunu güzel yapan şey sadece bir tuşa basmanız gerektiği ve ardından -nokuma seçeneği sayesinde senaryo devam ediyor . Bu örnekte, ANSdeğişkeni olarak komut dosyasını kapatmanız, yeniden başlatmanız veya basitçe çıkmanız isteniyor ve kullanıcının yalnızca E, R veya S tuşlarına basması yeterli çıkacak.

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

Bu Ubuntu'da hedeflendiğinden, arka planda debconf kullanmak için yapılandırılmış olanı kullanmanız gerekir. Debconf arka ucunu şununla bulabilirsiniz:

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

Eğer "diyalog" yazıyorsa, o zaman muhtemelen whiptailveya kullanır dialog. Lucid'de öyle whiptail.

Bu başarısız olursa, Dennis Williamson tarafından açıklandığı gibi bash "select" kullanın.


3
Bu, muhtemelen bu soru için çok azdır, fakat whiptail ve diyalogdan bahsettiği için +1! Bu komutların farkında değildim ... çok tatlı!
MestreLion

7
#! / Bin / sh
menüyü göster(){
    normal = `yankı" \ 033 [m "`
    menu = `echo" \ 033 [36m "` #Mavi
    number = `echo" \ 033 [33m "` #yellow
    bgred = `echo" \ 033 [41m "`
    fgred = `echo" \ 033 [31m "`
    printf "\ n $ {menu} ********************************************* *** $ {Normal} \ n"
    printf "$ {menu} ** $ {sayı} 1) $ {menu} Dağı dropbox $ {normal} \ n"
    printf "$ {menu} ** $ {sayı} 2) $ {menu} USB 500 Gig Sürücüyü Bağla $ {normal} \ n"
    printf "$ {menu} ** $ {sayı} 3) $ {menu} Apache'yi Yeniden Başlat $ {normal} \ n"
    printf "$ {menu} ** $ {sayı} 4) $ {menu} ssh Frost TomCat Sunucusu $ {normal} \ n"
    printf "$ {menu} ** $ {sayı} 5) $ {menu} Diğer bazı komutlar $ {normal} \ n"
    printf "$ {menu} ************************************************ * $ {Normal} \ n"
    printf "Lütfen bir menü seçeneği girin ve çıkmak için veya $ {fgred} x yazın. $ {normal}"
    tercih oku
}

{) (Option_picked
    msgcolor = `echo" \ 033 [01; 31m "` # koyu kırmızı
    normal = `eko" \ 033 [00; 00m "` # normal beyaz
    message = $ {@: - "$ {normal} Hata: Mesaj geçilmedi"}
    printf "$ {msgcolor} $ {message} $ {normal} \ n"
}

açık
menüyü göster
iken [$ opt! = '']
    yap
    if [$ opt = '']; sonra
      çıkış;
    Başka
      vaka $ kapsamı
        1) temizle;
            option_picked "Seçenek 1 Seçildi";
            printf "sudo mount / dev / sdh1 / mnt / DropBox /; # 3 terabayt";
            menüyü göster;
        ;;
        2) temizle;
            option_picked "Seçenek 2 Seçildi";
            printf "sudo mount / dev / sdi1 / mnt / usbDrive; # 500 gig sürücü";
            menüyü göster;
        ;;
        3) temizle;
            option_picked "Seçenek 3 Seçildi";
            printf "sudo hizmet apache2 yeniden başlat";
            menüyü göster;
        ;;
        4) temizle;
            option_picked "Seçenek 4 Seçildi";
            printf "ssh lmesser @ -p 2010";
            menüyü göster;
        ;;
        x) çıkış;
        ;;
        \ N) çıkış;
        ;;
        *)açık;
            option_picked "Menüden bir seçenek seçin";
            menüyü göster;
        ;;
      esac
    fi
tamam

2
Bunun eski olduğunu biliyorum, ama derlemek için #! / Bin / bash okumak için ilk satıra ihtiyaç var.
JClar

Kod incelemesi: $İfadedeki $optdeğişkenden eksik while. ifİfadesi gereksiz olduğunu. Tutarsız girintiler. Kullanılması menubazı yerlerde o show_menu' olması gereken yerde . show_menu` döngü üstünde koymak mümkündür yerine her birinde tekrarlanıyor case. Tutarsız girintiler. Tek köşeli ayraç kullanımı ve iki katına sahip olanları karıştırma. Bunun yerine kodlanmış ANSI dizilerinin kullanılması tput. Tüm harflerin var adlarının kullanılması önerilmez. FGREDaranmalı bgred. Bunun yerine backticks kullanımı $(). İşlev tanımı tutarlı olmalı ve kullanmamalısınız ...
Dennis Williamson 20:

... ikisi birlikte oluşur. Terminal noktalı virgül gerekli değildir. Bazı renkler vb. İki kez tanımlanmıştır. \nAsla dava açılmayacak. Belki daha fazla.
Dennis Williamson,

6

Ubuntu'da her zaman orada görünen Zenity'yi kullandım, çok iyi çalışıyor ve birçok özelliğe sahip. Bu, olası bir menünün taslağıdır:

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

Hata! Bu pasajın görünümü için özür dilerim, ilk kez gönderi ve belki de HTML'yi kapatmak zorunda görünüyor
LazyEchidna

Daha iyi, düzenleme 'kod örneği' açık, bunun için üzgünüm
LazyEchidna

5

Bash fantezi menüsü

Önce deneyin, sonra ayrıntılı açıklama için sayfamı ziyaret edin ... Dış kütüphanelere veya diyalog veya zenci gibi programlara gerek yok ...

#/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
Bu sadece bash, biraz havalı ile çalışır.
Layton Everson

2
Güzel! "daha sonra ayrıntılı açıklama için sayfamı ziyaret et" linki var mı?
meşe


2

Cevaplanan serverfault da zaten aynı soru var . Buradaki çözüm whiptail kullanıyor .


Teşekkürler, ama senaryomun genel tüketim için olduğu gibi, başka bağımlılıkları da olmasını istemiyorum. Ama kim bilir, gelecekte kullanmak için kim bilir.
Daniel Rodrigues

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.