เมนูแบบเลือกได้หลายแบบในสคริปต์ทุบตี


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

... และสคริปต์จะสิ้นสุดลง

ดังนั้นคำถามของฉัน - เป็นไปได้ในการทุบตีและถ้าเป็นเช่นนั้นทุกคนสามารถให้ตัวอย่างรหัสได้หรือไม่

คำแนะนำใด ๆ ที่จะได้รับการชื่นชมมาก

คำตอบ:


35

ฉันคิดว่าคุณควรจะดูที่โต้ตอบหรือwhiptail

กล่องโต้ตอบ

แก้ไข:

นี่คือตัวอย่างสคริปต์ที่ใช้ตัวเลือกจากคำถามของคุณ:

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

@ am2605: ดูการแก้ไขของฉัน ฉันเพิ่มสคริปต์ตัวอย่าง
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

3
มันเพียง แต่ดูซับซ้อนจนกว่าคุณจะได้ใช้มันครั้งหรือสองครั้งแล้วคุณจะไม่เคยใช้สิ่งอื่นใด ...
คริส S

27

หากคุณคิดว่าwhiptailมีความซับซ้อนนี่มันไปรหัสทุบตีเท่านั้นซึ่งไม่ตรงกับสิ่งที่คุณต้องการ มันสั้น (~ 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 () ในตัว ฉันไม่พบหลายคนที่รู้เกี่ยวกับมันมีอยู่ในทุบตี มีประโยชน์มากถ้ามีคนใช้รหัสใน 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!
ดร. 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

และ shebang #!/bin/kshไป


เยี่ยมมาก! จะจัดการให้ทำงานใน KSH ได้อย่างไร?
FuSsA

1
@FuSsA: ฉันแก้ไขคำตอบของฉันเพื่อแสดงการเปลี่ยนแปลงที่จำเป็นเพื่อให้ทำงานเป็น ksh
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

1
การจัดการอาร์เรย์ใน bash นั้นง่ายมาก คุณไม่ได้เป็นคนแรกเท่านั้นคุณเป็นคนเดียวที่มีน้ำหนักมากกว่า 40k ในทรินิตี้ทั้งหมด
peterh กล่าวว่าคืนสถานะโมนิก้า

1
@FuSsA: options=(*)(หรือรูปแบบวงกลมอื่น ๆ ) คุณจะได้รับรายชื่อไฟล์ในอาเรย์ ความท้าทายจากนั้นจะได้รับการเลือกเครื่องหมายอาร์เรย์ ( ${opts[@]}) ซิปพร้อมกับมัน มันสามารถทำได้ด้วยการforวนรอบ แต่มันจะต้องถูกเรียกใช้สำหรับการส่งแต่ละครั้งผ่านการwhileวนรอบนอก คุณอาจต้องการพิจารณาการใช้dialogหรือwhiptailตามที่ฉันกล่าวถึงในคำตอบอื่น ๆ ของฉัน - แม้ว่าสิ่งเหล่านี้เป็นการอ้างอิงภายนอก
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

1
@FuSsA: จากนั้นคุณสามารถบันทึกสตริงในอาร์เรย์อื่น (หรือใช้${opts[@]}และบันทึกสตริงที่ส่งผ่านเป็นอาร์กิวเมนต์เพิ่มเติมไปยังฟังก์ชันแทนที่จะเป็น+)
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

2

ฉันเขียนห้องสมุดชื่อแบบสอบถามซึ่งเป็น mini-DSL สำหรับการสร้างแบบสอบถามบรรทัดคำสั่ง มันแจ้งให้ผู้ใช้ตอบชุดคำถามและพิมพ์คำตอบไปที่ stdout

มันทำให้งานของคุณง่ายจริงๆ ติดตั้ง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จากนั้นเรียก เมื่อคุณตอบคำถามเสร็จแล้วพวกเขาจะพิมพ์ไปยัง stdout มันใช้งานได้กับ 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

คำตอบที่ยอดเยี่ยม เพิ่มบันทึกย่อเพื่อเพิ่มจำนวนเช่นตัวเลือก e 15 ที่n1 SELECTIONเป็นส่วนสำคัญที่จะเพิ่มจำนวนของตัวเลข ..
DBF

ลืมที่จะเพิ่ม; โดยที่-n2 SELECTIONจะยอมรับตัวเลขสองหลัก (เช่น 15) -n3ยอมรับสามหลัก (เช่น 153) และอื่น ๆ
dbf

1

นี่คือฟังก์ชั่นทุบตีที่อนุญาตให้ผู้ใช้เลือกหลายตัวเลือกด้วยปุ่มลูกศรและเว้นวรรคและยืนยันด้วย 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[@]}")'
}

คุณเรียกมันว่ายังไง? ไฟล์จะมีลักษณะอย่างไร
Eli


-1
export supermode=none

source easybashgui

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

2
บางทีคุณอาจเพิ่มคำอธิบายเล็กน้อยเกี่ยวกับสิ่งนี้กำลังทำอะไรอยู่? สำหรับผู้เยี่ยมชมในอนาคตไม่มากสำหรับ OP
slm

easybashguiนอกจากนี้ยังเชื่อมโยงไปยังจุดเริ่มต้นของการ
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.