Mehrfachauswahlmenü im Bash-Skript

28

Ich bin ein Bash-Neuling, möchte aber ein Skript erstellen, in dem der Benutzer mehrere Optionen aus einer Liste von Optionen auswählen kann.

Im Wesentlichen möchte ich etwas Ähnliches wie das folgende Beispiel:

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

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

Mein Skript hätte jedoch mehr Optionen, und ich möchte die Auswahl von Mehrfachwerten zulassen. So etwas in der Art:

1) Option 1
2) Option 2
3) Option 3
4) Option 4
5) Fertig

Ein Feedback zu den von ihnen ausgewählten Personen wäre ebenfalls großartig, z. B. Pluszeichen neben denen, die sie bereits ausgewählt haben. Wenn Sie beispielsweise "1" auswählen, möchten Sie zum Löschen und erneuten Drucken eine Seite erstellen:

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

Wenn Sie dann "3" auswählen:

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

Wenn sie erneut (1) auswählen, möchte ich, dass die Option "abgewählt" wird:

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

Und zum Schluss, wenn Fertig gedrückt wird, möchte ich eine Liste der ausgewählten, die angezeigt werden sollen, bevor das Programm beendet wird, z. B. wenn der aktuelle Status ist:

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

Drücken Sie 5, um Folgendes zu drucken:

Option 2, Option 3, Option 4

... und das Skript wird beendet.

Also meine Frage - ist das in bash möglich und wenn ja, kann jemand ein Codebeispiel bereitstellen?

Jeder Rat wäre sehr dankbar.

user38939
quelle

Antworten:

35

Ich denke, Sie sollten sich Dialog oder Peitschenschwanz ansehen .

Dialogbox

Bearbeiten:

Hier ist ein Beispielskript mit den Optionen aus Ihrer Frage:

#!/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
Bis auf weiteres angehalten.
quelle
Dank dafür. Sieht komplexer aus als ich gehofft hatte, aber ich werde es mir
ansehen
@ am2605: Siehe meine Bearbeitung. Ich habe ein Beispielskript hinzugefügt.
Bis auf weiteres angehalten.
3
Es sieht nur komplex aus, bis Sie es einmal oder zweimal verwendet haben, dann werden Sie nie etwas anderes verwenden ...
Chris S
27

Wenn Sie denken whiptail, dass es komplex ist, dann handelt es sich hier um einen reinen Bash-Code, der genau das tut , was Sie wollen. Es ist kurz (~ 20 Zeilen), aber für Anfänger etwas kryptisch. Neben "+" für aktivierte Optionen wird auch eine Rückmeldung für jede Benutzeraktion angezeigt ("ungültige Option", "Option X wurde aktiviert" / deaktiviert usw.).

Das heißt, los geht's!

Hoffe es gefällt euch ... es war eine lustige Herausforderung, es zu schaffen :)

#!/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
quelle
Gut gemacht! Gut gemacht!
Daniel
4
Dieser ist ein bisschen kryptisch, aber ich mag Ihre Verwendung komplexer Klammererweiterungen und dynamischer Arrays. Ich habe ein bisschen Zeit gebraucht, um alles nachlesen zu können, aber ich liebe es. Ich finde es auch toll, dass Sie die integrierte Funktion printf () verwendet haben. Ich finde nicht viele, die darüber Bescheid wissen, in Bash. Sehr praktisch, wenn man es gewohnt ist, in C zu codieren.
Yokai
1
Wenn jemand mehrere Optionen (durch Leerzeichen getrennt) gleichzeitig auswählen while menu && read -rp "$prompt" nums && [[ "$nums" ]]; do while read num; do ... done < <(echo $nums |sed "s/ /\n/g") done
möchte
1
Dies war sehr hilfreich bei der Entwicklung eines Skripts, das von mehreren anderen Personen verwendet wird, die keinen Zugriff auf Whiptail oder andere Pakete haben, weil sie git bashWindows verwenden!
Dr. Ivol
5

Hier ist eine Möglichkeit, genau das zu tun, was Sie möchten, indem Sie nur Bash-Funktionen ohne externe Abhängigkeiten verwenden. Es markiert die aktuelle Auswahl und ermöglicht es Ihnen, sie umzuschalten.

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

Ändern Sie für ksh die ersten beiden Zeilen der Funktion:

function choice {
    typeset choice=$1

und die shebang to #!/bin/ksh.

Bis auf weiteres angehalten.
quelle
Schönes Beispiel! Wie schafft man es, es in KSH auszuführen?
FuSsA
1
@FuSsA: Ich habe meine Antwort bearbeitet, um die Änderungen anzuzeigen, die erforderlich sind, damit sie in ksh funktioniert.
Bis auf weiteres angehalten.
1
Das Array-Handling in Bash ist sehr hardcore. Sie sind nicht nur der Erste, Sie sind der Einzige über 40 km in der gesamten Dreifaltigkeit.
Peter sagt, Monica
1
@FuSsA: options=(*)(oder andere Muster) zeigt eine Liste der Dateien im Array an. Die Herausforderung wäre dann, das Auswahlmarkierungsfeld ( ${opts[@]}) zusammen mit diesem zu komprimieren. Es kann mit einer forSchleife durchgeführt werden, aber es müsste für jeden Durchgang durch die äußere whileSchleife ausgeführt werden. Vielleicht möchten Sie die Verwendung von dialogoder erwägen, whiptailwie ich in meiner anderen Antwort erwähnt habe - obwohl dies externe Abhängigkeiten sind.
Bis auf weiteres angehalten.
1
@FuSsA: Dann könnten Sie die Zeichenfolge in einem anderen Array ${opts[@]}speichern (oder die Zeichenfolge verwenden und speichern, die statt als zusätzliches Argument an die Funktion übergeben wurde +).
Bis auf weiteres angehalten.
2

Ich habe eine Bibliothek namens Fragebogen geschrieben , eine Mini-DSL zum Erstellen von Kommandozeilen-Fragebögen. Sie fordert den Benutzer auf, eine Reihe von Fragen zu beantworten, und gibt die Antworten an stdout aus.

Es macht Ihre Aufgabe wirklich einfach. Installiere es mit pip install questionnaireund erstelle ein Skript, zB questions.pyso:

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

Dann lauf python questions.py. Wenn Sie die Fragen beantwortet haben, werden sie auf stdout gedruckt. Es funktioniert mit Python 2 und 3, von denen mit ziemlicher Sicherheit eines auf Ihrem System installiert ist.

Es kann auch viel kompliziertere Fragebögen verarbeiten, falls jemand dies tun möchte. Hier sind einige Funktionen:

  • Gibt Antworten als JSON (oder als Nur-Text) an stdout aus
  • Ermöglicht Benutzern, Fragen erneut zu beantworten
  • Unterstützt bedingte Fragen (Fragen können von vorherigen Antworten abhängen)
  • Unterstützt die folgenden Arten von Fragen: Rohdaten, wählen Sie eine aus, wählen Sie viele aus
  • Keine verbindliche Kopplung zwischen Fragendarstellung und Antwortwerten
kylebebak
quelle
1

Ich habe das Beispiel von MestreLion verwendet und den folgenden Code entworfen. Sie müssen lediglich die Optionen und Aktionen in den ersten beiden Abschnitten aktualisieren.

#!/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
quelle
Hervorragende Antwort. Fügen Sie auch eine Notiz zum Erhöhen der Zahl hinzu, z. B. Option 15; wo n1 SELECTIONist der entscheidende teil, um die
anzahl der
Ich habe vergessen hinzuzufügen; wo -n2 SELECTIONzwei Ziffern akzeptieren wird (zB 15), -n3übernimmt drei (zB 153), usw.
dbf
1

Hier ist eine Bash-Funktion, mit der Benutzer mehrere Optionen mit den Pfeiltasten und der Leertaste auswählen und mit der Eingabetaste bestätigen können. Es hat eine schöne Speisekarte. Ich habe es mit Hilfe von https://unix.stackexchange.com/a/415155 geschrieben . Man kann es so nennen:

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

Das Ergebnis wird als Array in einer Variablen gespeichert, wobei der Name als erstes Argument angegeben wird. Das letzte Argument ist optional und wird verwendet, um einige Optionen standardmäßig auszuwählen. Es sieht aus wie das.

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
quelle
wie nennst du das? Wie würde die Datei aussehen?
Eli
-1
export supermode=none

source easybashgui

list "Option 1" "Option 2" "Option 3" "Option 4"
user173209
quelle
2
Vielleicht könnten Sie eine kleine Beschreibung hinzufügen, was dies tut? Für zukünftige Besucher nicht so sehr für die OP.
Slm
Auch ein Link zur Herkunft von easybashgui.
Bis auf weiteres angehalten.