Menu wielokrotnego wyboru w skrypcie bash

28

Jestem nowicjuszem, ale chciałbym utworzyć skrypt, w którym chciałbym pozwolić użytkownikowi wybrać wiele opcji z listy opcji.

Zasadniczo chciałbym coś podobnego do poniższego przykładu:

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

(Źródło: http://www.faqs.org/docs/Linux-HOWTO/Bash-Prog-Intro-HOWTO.html#ss9.1 )

Jednak mój skrypt miałby więcej opcji i chciałbym zezwolić na wybór wielokrotności. Więc coś takiego:

1) Opcja 1
2) Opcja 2
3) Opcja 3
4) Opcja 4
5) Gotowe

Znakomity byłby też odzew na wybrane przez nich osoby, np. Znaki plus obok tych, które już wybrali. Np. Jeśli wybierzesz „1”, chcę wyczyścić stronę i ponownie wydrukować:

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

Następnie, jeśli wybierzesz „3”:

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

Ponadto, jeśli ponownie wybiorą (1), chcę, aby „odznaczyła” opcję:

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

I na koniec, po naciśnięciu Gotowe, chciałbym, aby lista wybranych została wyświetlona przed wyjściem z programu, np. Jeśli aktualny stan to:

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

Naciśnięcie 5 powinno wydrukować:

Option 2, Option 3, Option 4

... i skrypt się kończy.

Więc moje pytanie - czy jest to możliwe w bash, a jeśli tak, to czy ktoś może dostarczyć próbkę kodu?

Wszelkie porady będą mile widziane.

użytkownik38939
źródło

Odpowiedzi:

35

Myślę, że powinieneś spojrzeć na okno dialogowe lub whiptail .

Okno dialogowe

Edytować:

Oto przykładowy skrypt wykorzystujący opcje z twojego pytania:

#!/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
Wstrzymano do odwołania.
źródło
Dziękuję za to. Wygląda na bardziej skomplikowane, niż się spodziewałem, ale sprawdzę to :-)
user38939
@ am2605: Zobacz moją edycję. Dodałem przykładowy skrypt.
Wstrzymano do odwołania.
3
Wygląda na skomplikowane, dopóki nie użyjesz go raz lub dwa, wtedy nigdy nie użyjesz nic innego ...
Chris S
27

Jeśli uważasz, że whiptailjest skomplikowany, oto kod tylko do bash, który robi dokładnie to , co chcesz. Jest krótki (~ 20 linii), ale dla początkujących jest nieco tajemniczy. Oprócz pokazywania „+” dla zaznaczonych opcji, zapewnia również informację zwrotną dla każdej akcji użytkownika („nieprawidłowa opcja”, „opcja X została zaznaczona” / niezaznaczona itp.).

To powiedziawszy, proszę bardzo!

Mam nadzieję, że Ci się spodoba ... to było całkiem fajne wyzwanie :)

#!/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"
MestreLion
źródło
Dobra robota! Dobra robota!
Daniel
4
Ten jest nieco tajemniczy, ale uwielbiam korzystanie ze złożonych rozszerzeń nawiasów klamrowych i tablic dynamicznych. Zajęło mi trochę czasu, aby móc przeczytać wszystko, co się dzieje, ale uwielbiam to. Podoba mi się również to, że użyłeś wbudowanej funkcji printf (). Nie widzę wielu, którzy wiedzą o tym, istniejących w bash. Bardzo przydatny, jeśli ktoś jest przyzwyczajony do kodowania w C.
Yokai
1
Jeśli ktoś chciałby mieć możliwość wybrania wielu opcji (oddzielonych spacją) naraz:while menu && read -rp "$prompt" nums && [[ "$nums" ]]; do while read num; do ... done < <(echo $nums |sed "s/ /\n/g") done
TAAPSogeking
1
Było to bardzo przydatne w tworzeniu skryptu, który jest używany przez wiele innych osób, które nie mają dostępu do whiptail lub innych pakietów, ponieważ używają systemu git bashWindows!
Dr Ivol
5

Oto sposób na robienie dokładnie tego, co chcesz, używając tylko funkcji Bash bez zewnętrznych zależności. Oznacza bieżące wybory i pozwala je przełączać.

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

W przypadku ksh zmień pierwsze dwa wiersze funkcji:

function choice {
    typeset choice=$1

i shebang do #!/bin/ksh.

Wstrzymano do odwołania.
źródło
Niezły przykład! Jak zarządzać, aby uruchomić go w KSH?
FuSsA
1
@FuSsA: Zredagowałem swoją odpowiedź, aby pokazać zmiany potrzebne do jej działania w ksh.
Wstrzymano do odwołania.
1
Obsługa tablicy w bash jest bardzo hardcorowa. Jesteś nie tylko pierwszy, jesteś jedyny powyżej 40 tysięcy na całej trójcy.
Peter mówi, że przywraca Monikę
1
@FuSsA: options=(*)(lub inne wzorce globowania) otrzymają listę plików w tablicy. Wyzwaniem byłoby wówczas ${opts[@]}spakowanie razem tablicy znaków wyboru ( ). Można to zrobić za pomocą forpętli, ale trzeba by ją uruchamiać przy każdym przejściu przez zewnętrzną whilepętlę. Możesz rozważyć użycie dialoglub, whiptailjak wspomniałem w mojej innej odpowiedzi - chociaż są to zależności zewnętrzne.
Wstrzymano do odwołania.
1
@FuSsA: Następnie możesz zapisać ciąg znaków w innej tablicy (lub użyć ${opts[@]}i zapisać ciąg przekazany jako dodatkowy argument funkcji zamiast +).
Wstrzymano do odwołania.
2

Napisałem bibliotekę o nazwie kwestionariusz , który jest mini-DSL do tworzenia kwestionariuszy z linii poleceń. Skłania użytkownika do udzielenia odpowiedzi na szereg pytań i wypisuje odpowiedzi na standardowe wyjście.

To sprawia, że ​​twoje zadanie jest naprawdę łatwe. Zainstaluj go pip install questionnairei utwórz skrypt, na przykład questions.py:

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()

Potem biegnij python questions.py. Po zakończeniu odpowiadania na pytania są drukowane na standardowe wyjście. Działa z Python 2 i 3, z których jeden prawie na pewno jest zainstalowany w twoim systemie.

Może również obsługiwać znacznie bardziej skomplikowane kwestionariusze, na wypadek, gdyby ktoś chciał to zrobić. Oto niektóre funkcje:

  • Drukuje odpowiedzi jako JSON (lub jako zwykły tekst) na standardowe wyjście
  • Pozwala użytkownikom cofać się i ponownie odpowiadać na pytania
  • Obsługuje pytania warunkowe (pytania mogą zależeć od wcześniejszych odpowiedzi)
  • Obsługuje następujące rodzaje pytań: surowe dane wejściowe, wybierz jedno, wybierz wiele
  • Brak obowiązkowego łączenia między prezentacją pytania a wartościami odpowiedzi
kylebebak
źródło
1

Skorzystałem z przykładu z MestreLion i napisałem poniższy kod. Wszystko, co musisz zrobić, to zaktualizować opcje i działania w pierwszych dwóch sekcjach.

#!/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
Nathan Davieau
źródło
Doskonała odpowiedź. Dodaj także uwagę dotyczącą zwiększenia liczby, np. Opcja 15; gdzie n1 SELECTIONjest kluczowa część dla zwiększenia liczby cyfr.
dbf
Zapomniałem dodać; gdzie -n2 SELECTIONakceptuje dwie cyfry (np. 15), -n3akceptuje trzy (np. 153) itp.
dbf
1

Oto funkcja bash, która pozwala użytkownikowi wybrać wiele opcji za pomocą klawiszy strzałek i spacji i potwierdzić za pomocą Enter. Ma przyjemne menu. Napisałem to za pomocą https://unix.stackexchange.com/a/415155 . Można to nazwać tak:

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

Wynik jest przechowywany jako tablica w zmiennej o nazwie podanej jako pierwszy argument. Ostatni argument jest opcjonalny i służy do domyślnego wyboru niektórych opcji. To wygląda tak.

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[@]}")'
}
Denis Semenenko
źródło
jak to nazywasz? jak wyglądałby ten plik?
Eli
-1
export supermode=none

source easybashgui

list "Option 1" "Option 2" "Option 3" "Option 4"
użytkownik173209
źródło
2
Może mógłbyś dodać krótki opis tego, co to robi? Dla przyszłych gości, nie tyle dla PO.
slm
Również link do pochodzenia easybashgui.
Wstrzymano do odwołania.