bash脚本中的多选菜单


28

我是一名新手,但是我想创建一个脚本,允许用户从选项列表中选择多个选项。

本质上,我想要的是类似于以下示例的内容:

       #!/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中是否可能,如果可以,有人可以提供代码示例吗?

任何建议将不胜感激。

Answers:


35

我认为你应该看看对话wh

对话框

编辑:

这是一个使用问题选项的示例脚本:

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

感谢那。看起来比我希望的要复杂,但我会检查一下:-)
user38939 2010年

@ 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"

做得好!做得好!
丹尼尔(Daniel)

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
这在开发一个脚本时非常有用,该脚本可被其他因使用鞭打或其他软件包而无法访问的其他人使用 git bash在Windows!
Ivol博士

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循环都必须运行它。您可能要考虑使用dialogwhiptail正如我在其他答案中提到的那样-尽管这些是外部依赖项。
暂停,直到另行通知。

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(或纯文本)形式输出到stdout
  • 允许用户返回并回答问题
  • 支持条件性问题(问题可能取决于先前的答案)
  • 支持以下类型的问题:原始输入,选择一个,选择多个
  • 问题陈述和答案值之间没有强制性耦合

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确认。它具有类似菜单的感觉。我是在https://unix.stackexchange.com/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
也许您可以对此做些说明?对于未来的访客,OP并没有那么多。
slm

另外,还有的原点链接easybashgui
暂停,直到另行通知。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.