web-dev-qa-db-fra.com

Menu de sélection multiple dans le script bash

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

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

Cependant, mon script aurait plus d'options, et je voudrais 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é.

31
user38939

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

dialog box

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

Si vous pensez que whiptail est complexe, voici 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"
28
MestreLion

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.

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 questionnaire et créer un script, par exemple questions.py, comme ça:

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

Exécutez ensuite 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
2
kylebebak

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 https://unix.stackexchange.com/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[@]}")'
}
1
Denis Semenenko

Comme je n'ai pas trouvé d'alternative BASH appropriée pour Prompt-toolkit (python), dialoguer (Rust) ou enquêteur (nœud), j'ai essayé par moi-même:

https://i.stack.imgur.com/6AyAI.png

https://asciinema.org/a/Y4hLxnN20JtAlrn3hsC6dCRn8https://Gist.github.com/blurayne/f63c5a8521c0eeab8e9afd8baa45c65e

1
user2452171

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
1
Nathan Davieau