Menu di selezione multipla nello script bash


28

Sono un principiante bash ma vorrei creare uno script in cui vorrei consentire all'utente di selezionare più opzioni da un elenco di opzioni.

In sostanza quello che vorrei è qualcosa di simile all'esempio seguente:

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

(Fonte di http://www.faqs.org/docs/Linux-HOWTO/Bash-Prog-Intro-HOWTO.html#ss9.1 )

Tuttavia, la mia sceneggiatura avrebbe più opzioni e vorrei consentire la selezione dei multipli. Quindi qualcosa del genere:

1) Opzione 1
2) Opzione 2
3) Opzione 3
4) Opzione 4
5) Fine

Avere un feedback su quelli che hanno selezionato sarebbe anche fantastico, ad esempio segni più accanto a quelli che hanno già selezionato. Ad esempio, se selezioni "1", desidero effettuare la pagina per cancellare e ristampare:

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

Quindi se selezioni "3":

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

Inoltre, se hanno nuovamente selezionato (1) Vorrei che "deselezionasse" l'opzione:

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

Infine, quando viene premuto Fine, vorrei visualizzare un elenco di quelli selezionati prima della chiusura del programma, ad esempio se lo stato corrente è:

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

Premere 5 per stampare:

Option 2, Option 3, Option 4

... e lo script termina.

Quindi la mia domanda - è possibile in bash, e se è così qualcuno è in grado di fornire un esempio di codice?

Qualsiasi consiglio sarebbe molto apprezzato.

Risposte:


35

Penso che dovresti dare un'occhiata al dialogo o alla coda di cavallo .

la finestra di dialogo

Modificare:

Ecco uno script di esempio che utilizza le opzioni della tua domanda:

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

Grazie per quello Sembra più complesso di quanto avessi sperato, ma lo controllerò :-)
user38939,

@ am2605: vedi la mia modifica. Ho aggiunto uno script di esempio.
In pausa fino a nuovo avviso.

3
Sembra complesso fino a quando non lo hai usato una o due volte, quindi non userai mai nient'altro ...
Chris S,

27

Se pensi che whiptailsia complesso, qui va un codice solo bash che fa esattamente quello che vuoi. È breve (~ 20 righe), ma un po 'enigmatico per un principiante. Oltre a mostrare "+" per le opzioni selezionate, fornisce anche feedback per ogni azione dell'utente ("opzione non valida", "opzione X selezionata" / deselezionata, ecc.).

Detto questo, eccoti!

Spero ti piaccia ... è stata una sfida abbastanza divertente farlo :)

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

Buon lavoro! Buon lavoro!
Daniel,

4
Questo è un po 'enigmatico, ma adoro il tuo utilizzo di espansioni complesse di parentesi graffe e array dinamici. Mi ci è voluto un po 'di tempo per essere in grado di leggere tutto mentre succede, ma lo adoro. Adoro anche il fatto che tu abbia usato la funzione printf () integrata. Non ne trovo molti che ne siano a conoscenza bash. Molto utile se si è abituati a scrivere codice in C.
Yokai,

1
Se qualcuno volesse essere in grado di selezionare più opzioni (spazio separato) contemporaneamente:while menu && read -rp "$prompt" nums && [[ "$nums" ]]; do while read num; do ... done < <(echo $nums |sed "s/ /\n/g") done
TAAPSelezionando il

1
Questo è stato davvero utile nello sviluppo di uno script che viene utilizzato da più altre persone che non hanno accesso a whiptail o altri pacchetti perché stanno usando git bashsu Windows!
Dott. Ivol,

5

Ecco un modo per fare esattamente quello che vuoi usando solo le funzionalità di Bash senza dipendenze esterne. Contrassegna le selezioni correnti e consente di selezionarle.

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

Per ksh, cambia le prime due righe della funzione:

function choice {
    typeset choice=$1

e lo shebang a #!/bin/ksh.


Bel esempio! Come gestirlo in KSH?
FuSsA

1
@FuSsA: ho modificato la mia risposta per mostrare le modifiche necessarie per farlo funzionare in ksh.
In pausa fino a nuovo avviso.

1
La gestione dell'array in bash è molto hardcore. Non sei solo il primo, sei l'unico sopra i 40k dell'intera trinità.
Peter dice di reintegrare Monica il

1
@FuSsA: options=(*)(o altri schemi globbing) ti porterà un elenco di file nell'array. La sfida sarebbe quindi quella di comprimere l'array dei segni di selezione ( ${opts[@]}) insieme ad esso. Può essere fatto con un forloop, ma dovrebbe essere eseguito per ogni passaggio attraverso il whileloop esterno . Potresti prendere in considerazione l'utilizzo dialogo whiptailcome ho già detto nella mia altra risposta, anche se si tratta di dipendenze esterne.
In pausa fino a nuovo avviso.

1
@FuSsA: Quindi è possibile salvare la stringa in un altro array (o utilizzare ${opts[@]}e salvare la stringa, passata come argomento aggiuntivo alla funzione, anziché +).
In pausa fino a nuovo avviso.

2

Ho scritto una libreria chiamata questionario , che è un mini-DSL per la creazione di questionari da riga di comando. Chiede all'utente di rispondere a una serie di domande e stampa le risposte su stdout.

Rende il tuo compito davvero facile. Installalo con pip install questionnairee crea uno script, ad esempio questions.py, in questo modo:

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

Quindi corri python questions.py. Quando hai finito di rispondere alle domande, vengono stampate su stdout. Funziona con Python 2 e 3, uno dei quali è quasi sicuramente installato sul tuo sistema.

Può gestire anche questionari molto più complicati, nel caso qualcuno voglia farlo. Ecco alcune funzionalità:

  • Stampa le risposte come JSON (o come testo normale) su stdout
  • Consente agli utenti di tornare indietro e rispondere alle domande
  • Supporta domande condizionali (le domande possono dipendere da risposte precedenti)
  • Supporta i seguenti tipi di domande: input non elaborato, scegline uno, scegline molti
  • Nessun accoppiamento obbligatorio tra presentazione della domanda e valori di risposta

1

Ho usato l'esempio di MestreLion e ho redatto il codice seguente. Tutto quello che devi fare è aggiornare le opzioni e le azioni nelle prime due sezioni.

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

Risposta eccellente. Aggiungi anche una nota per aumentare il numero, ad es. Opzione 15; dov'è n1 SELECTIONla parte cruciale per aumentare il numero di cifre ..
dbf

Ho dimenticato di aggiungere; dove -n2 SELECTIONaccetta due cifre (ad es. 15), -n3accetta tre (ad es. 153), ecc.
dbf

1

Ecco una funzione bash che consente all'utente di selezionare più opzioni con i tasti freccia e Spazio e confermare con Invio. Ha una bella atmosfera simile a un menu. L'ho scritto con l'aiuto di https://unix.stackexchange.com/a/415155 . Può essere chiamato così:

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

Il risultato viene archiviato come una matrice in una variabile con il nome fornito come primo argomento. L'ultimo argomento è facoltativo e viene utilizzato per rendere alcune opzioni selezionate per impostazione predefinita. Sembra così

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[@]}")'
}

come lo chiami? come sarebbe il file?
Eli


-1
export supermode=none

source easybashgui

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

2
Forse potresti aggiungere una breve descrizione di ciò che sta facendo? Per i futuri visitatori, non tanto per l'OP.
slm,

Inoltre, un collegamento all'origine di easybashgui.
In pausa fino a nuovo avviso.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.