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

나는 당신이 대화p을 보아야한다고 생각한다 .

대화 상자

편집하다:

다음은 질문의 옵션을 사용하는 스크립트 예입니다.

#!/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
한두 번 사용하기 전까지는 복잡해 보인다면 절대 다른 것을 사용하지 않을 것입니다.
Chris S

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창에서 사용하기 때문에 채찍이나 다른 패키지에 액세스 할 수없는 여러 다른 사람들이 사용하는 스크립트를 개발하는 데 실제로 유용했습니다 !
박사 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

커맨드 라인 설문지를 작성하는 미니 DSL 인 questionnaire 라는 라이브러리 를 작성했습니다. 사용자에게 일련의 질문에 대답하고 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

훌륭한 답변입니다. 또한 숫자 15에 대한 메모를 추가하십시오 (예 : 옵션 15; n1 SELECTION자릿수를 늘리는 데 중요한 부분은 어디에 있습니까? .
dbf

추가하는 것을 잊었다; 여기서 -n2 SELECTION두 자리 숫자 (예 : 15), -n3세 자리 숫자 (예 : 153) 등을 허용합니다.
dbf

1

화살표 키와 스페이스를 사용하여 여러 옵션을 선택하고 Enter를 사용하여 확인할 수있는 bash 기능이 있습니다. 메뉴와 같은 느낌이 듭니다. 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


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.