Bash betiğinde çoklu seçim menüsü


28

Ben bir acemi acemiyim ama kullanıcının seçenekler listesinden birden fazla seçenek seçmesine izin vermek istediğim bir senaryo oluşturmak istiyorum.

Temelde istediğim aşağıdaki örneğe benzer bir şey:

       #!/bin/bash
       OPTIONS="Hello Quit"
       select opt in $OPTIONS; do
           if [ "$opt" = "Quit" ]; then
            echo done
            exit
           elif [ "$opt" = "Hello" ]; then
            echo Hello World
           else
            clear
            echo bad option
           fi
       done

( Http://www.faqs.org/docs/Linux-HOWTO/Bash-Prog-Intro-HOWTO.html#ss9.1 adresinden alınmıştır )

Ancak betiğimde daha fazla seçenek olacak ve katların seçilmesine izin vermek istiyorum. Yani böyle bir şey:

1) Seçenek 1
2) Seçenek 2
3) Seçenek 3
4) Seçenek 4
5) Tamam

Seçtikleri hakkında geri bildirim almak da harika olurdu, örneğin, daha önce seçtiklerinin yanındaki işaretler. Örneğin, "1" i seçerseniz, temizlemek ve yeniden yazdırmak için sayfa açmak istiyorum:

1) Option 1 +
2) Option 2
3) Option 3
4) Option 4
5) Done

Ardından "3" ü seçerseniz:

1) Option 1 +
2) Option 2
3) Option 3 +
4) Option 4
5) Done

Ayrıca, tekrar seçtilerse (1) seçeneğin "seçimini kaldırmasını" isterim:

1) Option 1
2) Option 2
3) Option 3 +
4) Option 4
5) Done

Ve nihayet Bitti düğmesine basıldığında, program bitmeden önce gösterilmek üzere seçilenlerin bir listesini istiyorum, örneğin şu andaki durum şuysa:

1) Option 1
2) Option 2 +
3) Option 3 + 
4) Option 4 +
5) Done

5 tuşuna basılması gerekir:

Option 2, Option 3, Option 4

... ve senaryo sonlandırılıyor.

Öyleyse benim sorum - bash olarak bu mümkün mü ve eğer öyleyse herhangi biri bir kod örneği sağlayabilecek mi?

Herhangi bir tavsiye çok takdir edilecektir.

Yanıtlar:


35

Bence diyaloga veya whiptail'a bakmalısın .

iletişim kutusu

Düzenle:

Sorunuzdaki seçenekleri kullanarak örnek bir komut dosyası:

#!/bin/bash
cmd=(dialog --separate-output --checklist "Select options:" 22 76 16)
options=(1 "Option 1" off    # any option can be set to default to "on"
         2 "Option 2" off
         3 "Option 3" off
         4 "Option 4" off)
choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty)
clear
for choice in $choices
do
    case $choice in
        1)
            echo "First Option"
            ;;
        2)
            echo "Second Option"
            ;;
        3)
            echo "Third Option"
            ;;
        4)
            echo "Fourth Option"
            ;;
    esac
done

Bunun için teşekkürler.
Umduğumdan

@ am2605: Düzenlememe bakın. Örnek bir komut dosyası ekledim.
sonraki duyuruya kadar duraklatıldı.

3
Sadece bir veya iki kere kullanana kadar karmaşık görünüyor, o zaman asla başka bir şey kullanmayacaksın ...
Chris S

27

whiptailKarmaşık olduğunu düşünüyorsanız , işte tam olarak istediğinizi yapan sadece bir bash kodu . Kısa (~ 20 satır), ancak bir başlangıç ​​için biraz şifreli. Kontrol edilen seçenekler için "+" göstermenin yanı sıra, her kullanıcı işlemi için geri bildirim sağlar ("geçersiz seçenek", "seçenek X kontrol edildi" / işaretsiz, vb.).

Dedi ki, işte gidiyorsunuz!

Umarım beğenirsin ... bu oldukça eğlenceli bir mücadele oldu :)

#!/bin/bash

# customize with your own.
options=("AAA" "BBB" "CCC" "DDD")

menu() {
    echo "Avaliable options:"
    for i in ${!options[@]}; do 
        printf "%3d%s) %s\n" $((i+1)) "${choices[i]:- }" "${options[i]}"
    done
    if [[ "$msg" ]]; then echo "$msg"; fi
}

prompt="Check an option (again to uncheck, ENTER when done): "
while menu && read -rp "$prompt" num && [[ "$num" ]]; do
    [[ "$num" != *[![:digit:]]* ]] &&
    (( num > 0 && num <= ${#options[@]} )) ||
    { msg="Invalid option: $num"; continue; }
    ((num--)); msg="${options[num]} was ${choices[num]:+un}checked"
    [[ "${choices[num]}" ]] && choices[num]="" || choices[num]="+"
done

printf "You selected"; msg=" nothing"
for i in ${!options[@]}; do 
    [[ "${choices[i]}" ]] && { printf " %s" "${options[i]}"; msg=""; }
done
echo "$msg"

Aferin! Aferin!
Daniel

4
Bu biraz şifreli ama karmaşık küme genişlemesi ve dinamik dizileri kullanımınıza bayılıyorum. Her şeyi olduğu gibi okuyabilmek biraz zaman aldı ama ben onu seviyorum. Ayrıca, printf () işlevini yerleşik olarak kullandığınız gerçeğini de seviyorum. Bash'ta var olanı bilen pek bir şey bulamıyorum. Biri C. kodlama için kullanılırsa Çok kullanışlı
Yokai

1
Birisi bir kerede birden fazla seçenek (boşlukla ayrılmış) while menu && read -rp "$prompt" nums && [[ "$nums" ]]; do while read num; do ... done < <(echo $nums |sed "s/ /\n/g") done
seçebilmek istiyorsa

1
Bu, git bashpencerelerde kullandıkları için whiptail veya diğer paketlere erişimi olmayan diğer birçok kişi tarafından kullanılan bir senaryo geliştirmede çok faydalı oldu !
Dr Ivol

5

İşte harici bağımlılığı olmayan sadece Bash özelliklerini kullanarak tam olarak ne yapmak istediğinizi yapmanın bir yolu. Geçerli seçimleri işaretler ve bunları değiştirmenize izin verir.

#!/bin/bash
# Purpose: Demonstrate usage of select and case with toggleable flags to indicate choices
# 2013-05-10 - Dennis Williamson

choice () {
    local choice=$1
    if [[ ${opts[choice]} ]] # toggle
    then
        opts[choice]=
    else
        opts[choice]=+
    fi
}

PS3='Please enter your choice: '
while :
do
    clear
    options=("Option 1 ${opts[1]}" "Option 2 ${opts[2]}" "Option 3 ${opts[3]}" "Done")
    select opt in "${options[@]}"
    do
        case $opt in
            "Option 1 ${opts[1]}")
                choice 1
                break
                ;;
            "Option 2 ${opts[2]}")
                choice 2
                break
                ;;
            "Option 3 ${opts[3]}")
                choice 3
                break
                ;;
            "Option 4 ${opts[4]}")
                choice 4
                break
                ;;
            "Done")
                break 2
                ;;
            *) printf '%s\n' 'invalid option';;
        esac
    done
done

printf '%s\n' 'Options chosen:'
for opt in "${!opts[@]}"
do
    if [[ ${opts[opt]} ]]
    then
        printf '%s\n' "Option $opt"
    fi
done

Ksh için, fonksiyonun ilk iki satırını değiştirin:

function choice {
    typeset choice=$1

ve shebang'a #!/bin/ksh.


Güzel örnek! KSH'de nasıl çalıştırılır?
FuSsA

1
@FuSsA: Ksh ile çalışması için gereken değişiklikleri göstermek için cevabımı düzenlemiştim.
sonraki duyuruya kadar duraklatıldı.

1
Bash'deki dizi kullanımı oldukça zor. Sen sadece bir ilkin değilsin, 40k üstü sadece üçte birsin.
peterh, Monica

1
@FuSsA: options=(*)(veya diğer globbing kalıpları), dizideki dosyaların bir listesini alır. Bu durumda zorluk, seçim işaretlerini array ( ${opts[@]}) ile birlikte sıkıştırmaktı. Bir fordöngü ile yapılabilir , ancak dış whiledöngü boyunca her geçiş için çalıştırılması gerekir . Bunları dış bağımlılıklar olsa dialogda kullanmayı veya whiptaildiğer cevabımda bahsettiğim gibi düşünebilirsiniz .
sonraki duyuruya kadar duraklatıldı.

1
@FuSsA: Daha sonra dizgiyi başka bir diziye ${opts[@]}kaydedebilirsiniz (veya yerine işlev için ek bir argüman olarak iletilen dizgiyi kullanın ve kaydedin +).
sonraki duyuruya kadar duraklatıldı.

2

Komut satırı anketleri oluşturmak için mini DSL olan anket adı verilen bir kütüphane yazdım . Kullanıcının bir dizi soruyu yanıtlamasını ister ve cevapları stdout'a yazdırır.

Görevinizi gerçekten çok kolaylaştırır. Şununla yükleyin pip install questionnaireve bir komut dosyası oluşturun, örneğin questions.py, şunun gibi:

from questionnaire import Questionnaire
q = Questionnaire(out_type='plain')

q.add_question('options', prompt='Choose some options', prompter='multiple',
               options=['Option 1', 'Option 2', 'Option 3', 'Option 4'], all=None)

q.run()

O zaman koş python questions.py. Soruları cevaplamayı tamamladığınızda stdout'a yazdırılırlar. Python 2 ve 3 ile çalışır, bunlardan biri sisteminize neredeyse kesinlikle yüklenmiştir.

Birisi bunu yapmak isterse, çok daha karmaşık anketlerle de başa çıkabilir. İşte bazı özellikler:

  • Yanıtları JSON (veya düz metin olarak) stdout'a yazdırır
  • Kullanıcıların geri dönüp soruları cevaplamalarına izin verir
  • Koşullu soruları destekler (sorular önceki cevaplara bağlı olabilir)
  • Aşağıdaki soru türlerini destekler: ham girdi, birini seç, çok birini seç
  • Soru sunumu ile cevap değerleri arasında zorunlu bir bağlantı yoktur

1

MestreLion'daki örneği kullandım ve aşağıdaki kodu hazırladım. Tek yapmanız gereken ilk iki bölümdeki seçenekleri ve eylemleri güncellemektir.

#!/bin/bash
#title:         menu.sh
#description:   Menu which allows multiple items to be selected
#author:        Nathan Davieau
#               Based on script from MestreLion
#created:       May 19 2016
#updated:       N/A
#version:       1.0
#usage:         ./menu.sh
#==============================================================================

#Menu options
options[0]="AAA"
options[1]="BBB"
options[2]="CCC"
options[3]="DDD"
options[4]="EEE"

#Actions to take based on selection
function ACTIONS {
    if [[ ${choices[0]} ]]; then
        #Option 1 selected
        echo "Option 1 selected"
    fi
    if [[ ${choices[1]} ]]; then
        #Option 2 selected
        echo "Option 2 selected"
    fi
    if [[ ${choices[2]} ]]; then
        #Option 3 selected
        echo "Option 3 selected"
    fi
    if [[ ${choices[3]} ]]; then
        #Option 4 selected
        echo "Option 4 selected"
    fi
    if [[ ${choices[4]} ]]; then
        #Option 5 selected
        echo "Option 5 selected"
    fi
}

#Variables
ERROR=" "

#Clear screen for menu
clear

#Menu function
function MENU {
    echo "Menu Options"
    for NUM in ${!options[@]}; do
        echo "[""${choices[NUM]:- }""]" $(( NUM+1 ))") ${options[NUM]}"
    done
    echo "$ERROR"
}

#Menu loop
while MENU && read -e -p "Select the desired options using their number (again to uncheck, ENTER when done): " -n1 SELECTION && [[ -n "$SELECTION" ]]; do
    clear
    if [[ "$SELECTION" == *[[:digit:]]* && $SELECTION -ge 1 && $SELECTION -le ${#options[@]} ]]; then
        (( SELECTION-- ))
        if [[ "${choices[SELECTION]}" == "+" ]]; then
            choices[SELECTION]=""
        else
            choices[SELECTION]="+"
        fi
            ERROR=" "
    else
        ERROR="Invalid option: $SELECTION"
    fi
done

ACTIONS

Mükemmel cevap Ayrıca, sayıyı arttırmak için bir not ekleyin, e..g Seçenek 15; n1 SELECTIONbasamak sayısını artırmak için önemli olan kısım nerede ..
dbf

Eklemeyi unuttum; nerede -n2 SELECTION(15 örneğin) iki basamağını kabul eder, -n3vb üç (örn 153) kabul eder
dbf

1

İşte kullanıcının ok tuşları ve Boşluk ile birden fazla seçenek seçmesine ve Enter ile onaylamasına izin veren bir bash işlevi. Güzel bir menü gibi hissediyorum vardır. Ben https://unix.stackexchange.com/a/415155 yardımı ile yazdım . Bu şekilde adlandırılabilir:

multiselect result "Option 1;Option 2;Option 3" "true;;true"

Sonuç, ilk argüman olarak verilen isimde bir değişkende bir dizi olarak saklanır. Son bağımsız değişken isteğe bağlıdır ve varsayılan olarak seçilen bazı seçenekleri yapmak için kullanılır. Bu gibi görünüyor.

function prompt_for_multiselect {

    # little helpers for terminal print control and key input
    ESC=$( printf "\033")
    cursor_blink_on()   { printf "$ESC[?25h"; }
    cursor_blink_off()  { printf "$ESC[?25l"; }
    cursor_to()         { printf "$ESC[$1;${2:-1}H"; }
    print_inactive()    { printf "$2   $1 "; }
    print_active()      { printf "$2  $ESC[7m $1 $ESC[27m"; }
    get_cursor_row()    { IFS=';' read -sdR -p $'\E[6n' ROW COL; echo ${ROW#*[}; }
    key_input()         {
      local key
      IFS= read -rsn1 key 2>/dev/null >&2
      if [[ $key = ""      ]]; then echo enter; fi;
      if [[ $key = $'\x20' ]]; then echo space; fi;
      if [[ $key = $'\x1b' ]]; then
        read -rsn2 key
        if [[ $key = [A ]]; then echo up;    fi;
        if [[ $key = [B ]]; then echo down;  fi;
      fi 
    }
    toggle_option()    {
      local arr_name=$1
      eval "local arr=(\"\${${arr_name}[@]}\")"
      local option=$2
      if [[ ${arr[option]} == true ]]; then
        arr[option]=
      else
        arr[option]=true
      fi
      eval $arr_name='("${arr[@]}")'
    }

    local retval=$1
    local options
    local defaults

    IFS=';' read -r -a options <<< "$2"
    if [[ -z $3 ]]; then
      defaults=()
    else
      IFS=';' read -r -a defaults <<< "$3"
    fi
    local selected=()

    for ((i=0; i<${#options[@]}; i++)); do
      selected+=("${defaults[i]}")
      printf "\n"
    done

    # determine current screen position for overwriting the options
    local lastrow=`get_cursor_row`
    local startrow=$(($lastrow - ${#options[@]}))

    # ensure cursor and input echoing back on upon a ctrl+c during read -s
    trap "cursor_blink_on; stty echo; printf '\n'; exit" 2
    cursor_blink_off

    local active=0
    while true; do
        # print options by overwriting the last lines
        local idx=0
        for option in "${options[@]}"; do
            local prefix="[ ]"
            if [[ ${selected[idx]} == true ]]; then
              prefix="[x]"
            fi

            cursor_to $(($startrow + $idx))
            if [ $idx -eq $active ]; then
                print_active "$option" "$prefix"
            else
                print_inactive "$option" "$prefix"
            fi
            ((idx++))
        done

        # user key control
        case `key_input` in
            space)  toggle_option selected $active;;
            enter)  break;;
            up)     ((active--));
                    if [ $active -lt 0 ]; then active=$((${#options[@]} - 1)); fi;;
            down)   ((active++));
                    if [ $active -ge ${#options[@]} ]; then active=0; fi;;
        esac
    done

    # cursor position back to normal
    cursor_to $lastrow
    printf "\n"
    cursor_blink_on

    eval $retval='("${selected[@]}")'
}

nasıl diyorsun? dosya nasıl görünür?
Eli


-1
export supermode=none

source easybashgui

list "Option 1" "Option 2" "Option 3" "Option 4"

2
Belki bunun ne yaptığını biraz açıklayabilirsin? Gelecekteki ziyaretçiler için, OP için o kadar da değil.
slm

Ayrıca, kökenine bir bağlantı easybashgui.
sonraki duyuruya kadar duraklatıldı.
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.