Menu de sélection multiple dans le script bash

28

Je suis un débutant bash mais je voudrais créer un script dans lequel j'aimerais permettre à l'utilisateur de sélectionner plusieurs options dans une liste d'options.

Essentiellement, ce que je voudrais, c'est quelque chose de similaire à l'exemple ci-dessous:

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

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

Cependant, mon script aurait plus d'options, et j'aimerais autoriser la sélection de multiples. Donc quelque chose comme ça:

1) Option 1
2) Option 2
3) Option 3
4) Option 4
5) Terminé

Il serait également utile d'avoir des commentaires sur ceux qu'ils ont sélectionnés, par exemple des signes plus à côté de ceux qu'ils ont déjà sélectionnés. Par exemple, si vous sélectionnez "1", je voudrais page pour effacer et réimprimer:

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

Ensuite, si vous sélectionnez "3":

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

De plus, s'ils ont de nouveau sélectionné (1), je voudrais qu'il "désélectionne" l'option:

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

Et enfin, lorsque vous appuyez sur Terminé, j'aimerais qu'une liste de ceux qui ont été sélectionnés soit affichée avant la fin du programme, par exemple si l'état actuel est:

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

Appuyez sur 5 pour imprimer:

Option 2, Option 3, Option 4

... et le script se termine.

Donc ma question - est-ce possible en bash, et si oui, quelqu'un peut-il fournir un exemple de code?

Tout conseil serait très apprécié.

user38939
la source

Réponses:

35

Je pense que vous devriez jeter un œil au dialogue ou au whiptail .

boite de dialogue

Modifier:

Voici un exemple de script utilisant les options de votre question:

#!/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
En pause jusqu'à nouvel ordre.
la source
Merci pour ça. Semble plus complexe que je ne l'avais espéré, mais je vais le vérifier :-)
user38939
@ am2605: Voir ma modification. J'ai ajouté un exemple de script.
pause jusqu'à nouvel ordre.
3
Il semble que complexe jusqu'à ce que vous avez utilisé une ou deux fois, alors vous ne serez jamais utiliser quoi que ce soit d' autre ...
Chris S
27

Si vous pensez que whiptailc'est complexe, ici, il va un code bash uniquement qui fait exactement ce que vous voulez. C'est court (~ 20 lignes), mais un peu énigmatique pour un débutant. En plus d'afficher "+" pour les options cochées, il fournit également des commentaires pour chaque action de l'utilisateur ("option non valide", "l'option X a été cochée" / décochée, etc.).

Cela dit, c'est parti!

J'espère que vous apprécierez ... c'était un défi assez amusant de le faire :)

#!/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
la source
Bon travail! Bon travail!
Daniel
4
Celui-ci est un peu cryptique mais j'aime votre utilisation des extensions d'accolades complexes et des tableaux dynamiques. Il m'a fallu un peu de temps pour pouvoir tout lire en temps réel mais j'adore ça. J'aime aussi le fait que vous ayez utilisé la fonction printf () intégrée. Je n'en trouve pas beaucoup qui en connaissent l'existence dans bash. Très pratique si l'on est habitué au codage en C.
Yokai
1
Si quelqu'un voulait pouvoir sélectionner plusieurs options (séparées par des espaces) à la fois:while menu && read -rp "$prompt" nums && [[ "$nums" ]]; do while read num; do ... done < <(echo $nums |sed "s/ /\n/g") done
TAAPSogeking
1
Cela a été très utile pour développer un script qui est utilisé par plusieurs autres personnes qui n'ont pas accès à whiptail ou à d'autres packages car ils utilisent git bashsur Windows!
Dr Ivol
5

Voici un moyen de faire exactement ce que vous voulez en utilisant uniquement les fonctionnalités de Bash sans dépendances externes. Il marque les sélections actuelles et vous permet de les basculer.

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

Pour ksh, changez les deux premières lignes de la fonction:

function choice {
    typeset choice=$1

et le shebang #!/bin/ksh.

En pause jusqu'à nouvel ordre.
la source
Bel exemple! Comment réussir à l'exécuter dans KSH?
FuSsA
1
@FuSsA: J'ai édité ma réponse pour montrer les changements nécessaires pour le faire fonctionner dans ksh.
pause jusqu'à nouvel ordre.
1
La gestion des tableaux en bash est très hardcore. Vous n'êtes pas seulement le premier, vous êtes le seul au-dessus de 40k sur toute la Trinité.
Peterh dit de réintégrer Monica
1
@FuSsA: options=(*)(ou d'autres modèles de globbing) vous obtiendrez une liste de fichiers dans le tableau. Le défi serait alors d'obtenir le tableau de marques de sélection ( ${opts[@]}) zippé avec lui. Cela peut être fait avec une forboucle, mais il devrait être exécuté pour chaque passage dans la whileboucle externe . Vous voudrez peut-être envisager d'utiliser dialogou whiptailcomme je l'ai mentionné dans mon autre réponse - bien qu'il s'agisse de dépendances externes.
pause jusqu'à nouvel ordre.
1
@FuSsA: Ensuite, vous pouvez enregistrer la chaîne dans un autre tableau (ou utiliser ${opts[@]}et enregistrer la chaîne, passée comme argument supplémentaire à la fonction, au lieu de +).
pause jusqu'à nouvel ordre.
2

J'ai écrit une bibliothèque appelée questionnaire , qui est un mini-DSL pour créer des questionnaires en ligne de commande. Il invite l'utilisateur à répondre à une série de questions et imprime les réponses à stdout.

Cela rend votre tâche vraiment facile. Installez-le avec pip install questionnaireet créez un script, par exemple questions.py, comme ceci:

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

Ensuite, exécutez python questions.py . Lorsque vous avez terminé de répondre aux questions, elles sont imprimées sur stdout. Il fonctionne avec Python 2 et 3, dont l'un est presque certainement installé sur votre système.

Il peut également traiter des questionnaires beaucoup plus compliqués, au cas où quelqu'un voudrait le faire. Voici quelques fonctionnalités:

  • Imprime les réponses en JSON (ou en texte brut) sur stdout
  • Permet aux utilisateurs de revenir en arrière et de répondre aux questions
  • Prend en charge les questions conditionnelles (les questions peuvent dépendre des réponses précédentes)
  • Prend en charge les types de questions suivants: entrée brute, choisissez une, choisissez plusieurs
  • Pas de couplage obligatoire entre la présentation des questions et les valeurs des réponses
kylebebak
la source
1

J'ai utilisé l'exemple de MestreLion et rédigé le code ci-dessous. Il vous suffit de mettre à jour les options et les actions des deux premières sections.

#!/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
la source
Excellente réponse. Ajoutez également une note pour augmenter le nombre, e..g Option 15; où n1 SELECTIONest la partie cruciale pour augmenter le nombre de chiffres ..
dbf
J'ai oublié d'ajouter; où -n2 SELECTIONacceptera deux chiffres (par exemple 15), -n3accepte trois (par exemple 153), etc.
dbf
1

Voici une fonction bash qui permet à l'utilisateur de sélectionner plusieurs options avec les touches fléchées et l'espace et de confirmer avec Entrée. Il a une belle sensation de menu. Je l'ai écrit avec l'aide de /unix//a/415155 . Il peut être appelé comme ceci:

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

Le résultat est stocké sous forme de tableau dans une variable avec le nom fourni comme premier argument. Le dernier argument est facultatif et est utilisé pour rendre certaines options sélectionnées par défaut. Cela ressemble à ceci.

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
la source
comment l'appelez-vous? à quoi ressemblerait le fichier?
Eli
-1
export supermode=none

source easybashgui

list "Option 1" "Option 2" "Option 3" "Option 4"
user173209
la source
2
Peut-être pourriez-vous ajouter une petite description de ce que cela fait? Pour les futurs visiteurs, pas tant pour l'OP.
slm
Aussi, un lien vers l'origine de easybashgui.
pause jusqu'à nouvel ordre.