Многофункциональное меню в скрипте bash


28

Я новичок в Bash, но я хотел бы создать скрипт, в котором я бы хотел, чтобы пользователь мог выбрать несколько вариантов из списка вариантов.

По сути, я хотел бы что-то похожее на пример ниже:

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

(Источник: http://www.faqs.org/docs/Linux-HOWTO/Bash-Prog-Intro-HOWTO.html#ss9.1 )

Однако в моем сценарии было бы больше опций, и я бы хотел, чтобы были выбраны кратные числа. Так что-то вроде этого:

1) Вариант 1
2) Вариант 2
3) Вариант 3
4) Вариант 4
5) Готово

Было бы замечательно иметь отзывы о тех, кого они выбрали, например, плюсики рядом с теми, которые они уже выбрали. Например, если вы выберете «1», я бы хотел, чтобы страницу очистили и перепечатали:

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

Затем, если вы выберете «3»:

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

Кроме того, если они снова выбрали (1), я бы хотел, чтобы он отменил выбор опции:

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

И наконец, когда нажата кнопка «Готово», я бы хотел, чтобы список тех, которые были выбраны, отображался до выхода из программы, например, если текущее состояние:

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

Нажатие 5 должно напечатать:

Option 2, Option 3, Option 4

... и сценарий завершается.

Итак, мой вопрос - возможно ли это в bash, и если да, то может ли кто-нибудь предоставить пример кода?

Любые советы будут высоко ценится.

Ответы:


35

Я думаю, вы должны взглянуть на диалог или кнут .

чат

Редактировать:

Вот пример сценария, использующего параметры из вашего вопроса:

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

Спасибо за это. Выглядит сложнее, чем я
ожидал

@ am2605: см. мое редактирование. Я добавил пример сценария.
Приостановлено до дальнейшего уведомления.

3
Это выглядит сложным, пока вы не используете его один или два раза, тогда вы больше ничего не будете использовать ...
Крис С

27

Если вы думаете, что whiptailэто сложно, то здесь идет код только для bash, который делает именно то , что вы хотите. Это коротко (~ 20 строк), но немного загадочно для начинающего. Помимо отображения «+» для отмеченных опций, он также предоставляет обратную связь для каждого действия пользователя («недопустимая опция», «опция X была отмечена» / не отмечена и т. Д.).

Тем не менее, вы идете!

Надеюсь, вам понравится ... это было довольно весело сделать это :)

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

Отличная работа! Отличная работа!
Даниэль

4
Это немного загадочно, но мне нравится ваше использование сложных расширений и динамических массивов. Мне потребовалось немного времени, чтобы прочитать все, как это происходит, но мне это нравится. Мне также нравится тот факт, что вы использовали встроенную функцию printf (). Я не нахожу многих, кто знает об этом, в bash. Очень удобно, если вы привыкли к кодированию на C.
Yokai

1
Если кто-то хочет иметь возможность выбрать несколько вариантов (разделенных пробелами) одновременно:while menu && read -rp "$prompt" nums && [[ "$nums" ]]; do while read num; do ... done < <(echo $nums |sed "s/ /\n/g") done
TAAPSogeking

1
Это было действительно полезно при разработке сценария, который используется множеством других людей, которые не имеют доступа к whiptail или другим пакетам, потому что они используют git bashв Windows!
Доктор Ивол

5

Вот способ сделать именно то, что вы хотите, используя только функции Bash без внешних зависимостей. Он отмечает текущие выборы и позволяет переключать их.

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

Для ksh измените первые две строки функции:

function choice {
    typeset choice=$1

и Шебанг в #!/bin/ksh.


Отличный пример! Как управлять этим в KSH?
FuSsA

1
@FuSsA: я отредактировал свой ответ, чтобы показать изменения, необходимые для его работы в ksh.
Приостановлено до дальнейшего уведомления.

1
Обработка массивов в bash очень хардкорная. Вы не только первый, вы единственный, кто выше 40k во всей троице.
Петер говорит восстановить Монику

1
@FuSsA: options=(*)(или другие шаблоны глобализации ) получат список файлов в массиве. Сложной задачей было бы получить массив меток выделения ( ${opts[@]}), упакованный вместе с ним. Это можно сделать с помощью forцикла, но его нужно будет выполнять для каждого прохода через внешний whileцикл. Возможно, вы захотите рассмотреть возможность использования dialogили, whiptailкак я уже упоминал в моем другом ответе - хотя это внешние зависимости.
Приостановлено до дальнейшего уведомления.

1
@FuSsA: Затем вы можете сохранить строку в другом массиве (или использовать ${opts[@]}и сохранить строку, переданную в качестве дополнительного аргумента функции вместо +).
Приостановлено до дальнейшего уведомления.

2

Я написал библиотеку под названием Анкета , которая представляет собой мини-DSL для создания анкет из командной строки. Он предлагает пользователю ответить на ряд вопросов и распечатывает ответы на стандартный вывод.

Это делает вашу задачу действительно легкой. Установите его pip install questionnaireи создайте скрипт, например 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()

Тогда беги python questions.py. Когда вы закончите отвечать на вопросы, они будут напечатаны на стандартный вывод. Он работает с Python 2 и 3, один из которых почти наверняка установлен в вашей системе.

Он может обрабатывать и более сложные анкеты, если кто-то захочет это сделать. Вот некоторые особенности:

  • Выводит ответы в формате JSON (или в виде обычного текста) на стандартный вывод
  • Позволяет пользователям возвращаться и повторно отвечать на вопросы
  • Поддерживает условные вопросы (вопросы могут зависеть от предыдущих ответов)
  • Поддерживает следующие типы вопросов: необработанный ввод, выберите один, выберите много
  • Нет обязательной связи между представлением вопроса и значениями ответа

1

Я использовал пример из MestreLion и набросал код ниже. Все, что вам нужно сделать, это обновить параметры и действия в первых двух разделах.

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

Отличный ответ. Также добавьте примечание для увеличения числа, например, вариант 15; где n1 SELECTIONважная часть для увеличения количества цифр ..
DBF

Забыл добавить; где -n2 SELECTIONбудет принимать две цифры (например, 15), -n3принимает три (например, 153) и т. д.
dbf

1

Вот функция bash, которая позволяет пользователю выбрать несколько опций с помощью клавиш со стрелками и пробела, и подтвердить с помощью Enter. У этого есть хорошее подобное меню чувство. Я написал это с помощью /unix//a/415155 . Это можно назвать так:

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

Результат сохраняется в виде массива в переменной с именем, указанным в качестве первого аргумента. Последний аргумент является необязательным и используется для выбора некоторых параметров по умолчанию. Похоже на это.

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
export supermode=none

source easybashgui

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

2
Может быть, вы могли бы добавить небольшое описание того, что это делает? Для будущих посетителей, не так много для ОП.
СЛМ

Также ссылка на происхождение easybashgui.
Приостановлено до дальнейшего уведомления.
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.