Menu đa lựa chọn trong tập lệnh bash


28

Tôi là người mới chơi bash nhưng tôi muốn tạo một tập lệnh trong đó tôi muốn cho phép người dùng chọn nhiều tùy chọn từ danh sách các tùy chọn.

Về cơ bản những gì tôi muốn là một cái gì đó tương tự như ví dụ dưới đây:

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

(Được cung cấp từ http://www.faqs.org/docs/Linux-HOWTO/Bash-Prog-Intro-HOWTO.html#ss9.1 )

Tuy nhiên, tập lệnh của tôi sẽ có nhiều tùy chọn hơn và tôi muốn cho phép nhiều bội số được chọn. Vì vậy, một cái gì đó như thế này:

1) Tùy chọn 1
2) Tùy chọn 2
3) Tùy chọn 3
4) Tùy chọn 4
5) Xong

Có phản hồi về những cái họ đã chọn cũng sẽ rất tuyệt, ví dụ như dấu cộng bên cạnh những cái họ đã chọn. Ví dụ: nếu bạn chọn "1" tôi muốn trang để xóa và in lại:

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

Sau đó, nếu bạn chọn "3":

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

Ngoài ra, nếu họ chọn lại (1) tôi muốn "bỏ chọn" tùy chọn:

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

Và cuối cùng khi Xong được nhấn Tôi muốn một danh sách những cái đã được chọn sẽ được hiển thị trước khi chương trình thoát, ví dụ nếu trạng thái hiện tại là:

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

Nhấn 5 nên in:

Option 2, Option 3, Option 4

... và kịch bản chấm dứt.

Vì vậy, câu hỏi của tôi - điều này có thể có trong bash không, và nếu có ai có thể cung cấp một mẫu mã không?

Bât cư lơi khuyên nao cung se được đanh gia cao.

Câu trả lời:


35

Tôi nghĩ bạn nên xem qua hộp thoại hoặc whiptail .

hộp thoại

Chỉnh sửa:

Đây là một kịch bản ví dụ sử dụng các tùy chọn từ câu hỏi của bạn:

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

Cảm ơn vì điều đó. Trông phức tạp hơn tôi mong đợi, nhưng tôi sẽ kiểm tra xem :-)
user38939

@ am2605: Xem chỉnh sửa của tôi. Tôi đã thêm một kịch bản ví dụ.
Tạm dừng cho đến khi có thông báo mới.

3
Nó chỉ trông phức tạp cho đến khi bạn sử dụng nó một hoặc hai lần, sau đó bạn sẽ không bao giờ sử dụng bất cứ thứ gì khác ...
Chris S

27

Nếu bạn nghĩ whiptaillà phức tạp, thì đây là mã chỉ bash thực hiện chính xác những gì bạn muốn. Nó ngắn (~ 20 dòng), nhưng hơi khó hiểu đối với người mới bắt đầu. Bên cạnh việc hiển thị "+" cho các tùy chọn đã chọn, nó cũng cung cấp phản hồi cho từng hành động của người dùng ("tùy chọn không hợp lệ", "tùy chọn X đã được chọn" / không được chọn, v.v.).

Điều đó nói rằng, có bạn đi!

Hy vọng bạn thích ... đó là một thử thách khá thú vị để thực hiện nó :)

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

Làm tốt lắm! Làm tốt lắm!
Daniel

4
Đây là một chút khó hiểu nhưng tôi thích cách sử dụng của bạn mở rộng niềng răng phức tạp và mảng động. Tôi đã mất một chút thời gian để có thể đọc mọi thứ như nó xảy ra nhưng tôi thích nó. Tôi cũng thích thực tế là bạn đã sử dụng hàm printf () tích hợp. Tôi không tìm thấy nhiều người biết về nó tồn tại trong bash. Rất tiện dụng nếu một người được sử dụng để mã hóa trong C.
Yokai

1
Nếu bất cứ ai muốn có thể chọn nhiều tùy chọn (tách biệt không gian) cùng một lúc:while menu && read -rp "$prompt" nums && [[ "$nums" ]]; do while read num; do ... done < <(echo $nums |sed "s/ /\n/g") done
TAAPSogeking

1
Điều này thực sự hữu ích trong việc phát triển một tập lệnh được sử dụng bởi nhiều người khác, những người không có quyền truy cập vào whiptail hoặc các gói khác vì họ đang sử dụng git bashtrên windows!
Bác sĩ Ivol

5

Đây là một cách để làm chính xác những gì bạn muốn chỉ sử dụng các tính năng Bash mà không phụ thuộc bên ngoài. Nó đánh dấu các lựa chọn hiện tại và cho phép bạn chuyển đổi chúng.

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

Đối với ksh, thay đổi hai dòng đầu tiên của hàm:

function choice {
    typeset choice=$1

và các shebang để #!/bin/ksh.


Đẹp mẫu mực! Làm thế nào để quản lý để chạy nó trong KSH?
FuSsA

1
@FuSsA: Tôi đã chỉnh sửa câu trả lời của mình để hiển thị những thay đổi cần thiết để làm cho nó hoạt động trong ksh.
Tạm dừng cho đến khi có thông báo mới.

1
Việc xử lý mảng trong bash là rất khó. Bạn không chỉ là người đầu tiên, bạn là người duy nhất trên 40k trên toàn bộ ba.
peterh nói phục hồi Monica

1
@FuSsA: options=(*)(hoặc các mẫu hình cầu khác) sẽ giúp bạn có một danh sách các tệp trong mảng. Thử thách sau đó sẽ là mảng đánh dấu lựa chọn ( ${opts[@]}) được nén cùng với nó. Nó có thể được thực hiện với một forvòng lặp, nhưng nó sẽ phải được chạy cho mỗi lần đi qua whilevòng lặp bên ngoài . Bạn có thể muốn xem xét sử dụng dialoghoặc whiptailnhư tôi đã đề cập trong câu trả lời khác của tôi - mặc dù đây là những phụ thuộc bên ngoài.
Tạm dừng cho đến khi có thông báo mới.

1
@FuSsA: Sau đó, bạn có thể lưu chuỗi trong một mảng khác (hoặc sử dụng ${opts[@]}và lưu chuỗi, được truyền dưới dạng đối số bổ sung cho hàm, thay vì +).
Tạm dừng cho đến khi có thông báo mới.

2

Tôi đã viết một thư viện gọi là bảng câu hỏi , đó là một DSL nhỏ để tạo bảng câu hỏi dòng lệnh. Nó nhắc người dùng trả lời một loạt các câu hỏi và in câu trả lời cho thiết bị xuất chuẩn.

Nó làm cho nhiệm vụ của bạn thực sự dễ dàng. Cài đặt nó với pip install questionnairevà tạo một tập lệnh, ví dụ questions.pynhư thế này:

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

Sau đó chạy python questions.py. Khi bạn hoàn thành việc trả lời các câu hỏi, chúng được in ra thiết bị xuất chuẩn. Nó hoạt động với Python 2 và 3, một trong số đó gần như chắc chắn được cài đặt trên hệ thống của bạn.

Nó có thể xử lý các câu hỏi phức tạp hơn nhiều, trong trường hợp bất cứ ai muốn làm điều này. Dưới đây là một số tính năng:

  • In câu trả lời dưới dạng JSON (hoặc dưới dạng văn bản thuần túy) sang thiết bị xuất chuẩn
  • Cho phép người dùng quay lại và trả lời các câu hỏi
  • Hỗ trợ các câu hỏi có điều kiện (câu hỏi có thể phụ thuộc vào câu trả lời trước)
  • Hỗ trợ các loại câu hỏi sau: đầu vào thô, chọn một, chọn nhiều
  • Không có khớp nối bắt buộc giữa trình bày câu hỏi và giá trị câu trả lời

1

Tôi đã sử dụng ví dụ từ MestreLion và phác thảo mã dưới đây. Tất cả bạn cần làm là cập nhật các tùy chọn và hành động trong hai phần đầu tiên.

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

Câu trả lời tuyệt vời. Đồng thời thêm ghi chú để tăng số lượng, e..g Tùy chọn 15; đâu n1 SELECTIONlà phần cốt yếu để tăng số lượng chữ số ..
dbf

Quên thêm; nơi -n2 SELECTIONsẽ chấp nhận hai chữ số (ví dụ 15), -n3chấp nhận ba chữ số (ví dụ 153), v.v.
dbf

1

Đây là một hàm bash cho phép người dùng chọn nhiều tùy chọn bằng phím mũi tên và Dấu cách và xác nhận bằng Enter. Nó có một cảm giác như menu đẹp. Tôi đã viết nó với sự giúp đỡ của https://unix.stackexchange.com/a/415155 . Nó có thể được gọi như thế này:

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

Kết quả được lưu trữ dưới dạng một mảng trong một biến với tên được cung cấp làm đối số đầu tiên. Đối số cuối cùng là tùy chọn và được sử dụng để tạo một số tùy chọn được chọn theo mặc định. Có vẻ như thế này.

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[@]}")'
}

bạn gọi cái đó như thế nào? tập tin sẽ như thế nào?
Eli


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.