화살표 키/메뉴 진입

화살표 키/메뉴 진입

3가지 옵션을 표시하고 사용자가 화살표 키를 사용하여 강조 표시 커서를 이동하고 Enter 키를 눌러 옵션을 선택하는 메뉴를 쉘 스크립트에서 어떻게 만들 수 있습니까?

답변1

이는 bash기능적 형태의 순수한 스크립팅 솔루션 으로 select_option, 다음에만 의존합니다.ANSI 이스케이프 시퀀스그리고 내장 read.

OSX의 Bash 4.2.45에서 사용할 수 있습니다. 내가 아는 한, 모든 환경에서 똑같이 잘 작동하지 않을 수 있는 펑키한 부분은 get_cursor_row()( key_input()위/아래 키 감지용) 및 cursor_to()기능입니다.

#!/usr/bin/env bash

# Renders a text based list of options that can be selected by the
# user using up, down and enter keys and returns the chosen option.
#
#   Arguments   : list of options, maximum of 256
#                 "opt1" "opt2" ...
#   Return value: selected index (0 for opt1, 1 for opt2 ...)
function select_option {

    # 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_option()     { printf "   $1 "; }
    print_selected()   { printf "  $ESC[7m $1 $ESC[27m"; }
    get_cursor_row()   { IFS=';' read -sdR -p $'\E[6n' ROW COL; echo ${ROW#*[}; }
    key_input()        { read -s -n3 key 2>/dev/null >&2
                         if [[ $key = $ESC[A ]]; then echo up;    fi
                         if [[ $key = $ESC[B ]]; then echo down;  fi
                         if [[ $key = ""     ]]; then echo enter; fi; }

    # initially print empty new lines (scroll down if at bottom of screen)
    for opt; do printf "\n"; done

    # determine current screen position for overwriting the options
    local lastrow=`get_cursor_row`
    local startrow=$(($lastrow - $#))

    # 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 selected=0
    while true; do
        # print options by overwriting the last lines
        local idx=0
        for opt; do
            cursor_to $(($startrow + $idx))
            if [ $idx -eq $selected ]; then
                print_selected "$opt"
            else
                print_option "$opt"
            fi
            ((idx++))
        done

        # user key control
        case `key_input` in
            enter) break;;
            up)    ((selected--));
                   if [ $selected -lt 0 ]; then selected=$(($# - 1)); fi;;
            down)  ((selected++));
                   if [ $selected -ge $# ]; then selected=0; fi;;
        esac
    done

    # cursor position back to normal
    cursor_to $lastrow
    printf "\n"
    cursor_blink_on

    return $selected
}

사용 예는 다음과 같습니다.

echo "Select one option using up/down keys and enter to confirm:"
echo

options=("one" "two" "three")

select_option "${options[@]}"
choice=$?

echo "Choosen index = $choice"
echo "        value = ${options[$choice]}"

출력은 다음과 같습니다. 현재 선택된 옵션을 강조 표시하는 데 역ANSI 색상이 사용됩니다(Markdown에서는 전달하기 어렵습니다). 필요한 경우 print_selected()이 기능에서 조정할 수 있습니다 .

Select one option using up/down keys and enter to confirm:

  [one] 
   two 
   three 

고쳐 쓰다:select_optselect_option다음은 명령문에서 더 쉽게 사용할 수 있도록 위의 함수를 래핑하는 작은 확장입니다 case.

function select_opt {
    select_option "$@" 1>&2
    local result=$?
    echo $result
    return $result
}

3가지 텍스트 옵션이 있는 사용 예:

case `select_opt "Yes" "No" "Cancel"` in
    0) echo "selected Yes";;
    1) echo "selected No";;
    2) echo "selected Cancel";;
esac

$?알려진 항목(이 경우 예 및 아니요)이 있는 경우 상황을 혼합하고 와일드카드 케이스에 대한 종료 코드를 활용할 수도 있습니다 .

options=("Yes" "No" "${array[@]}") # join arrays to add some variable array
case `select_opt "${options[@]}"` in
    0) echo "selected Yes";;
    1) echo "selected No";;
    *) echo "selected ${options[$?]}";;
esac

답변2

대화당신이 달성하고 싶은 것을 달성하기 위한 훌륭한 도구입니다. 다음은 간단한 세 가지 선택 메뉴의 예입니다.

dialog --menu "Choose one:" 10 30 3 \
    1 Red \
    2 Green \
    3 Blue

구문은 다음과 같습니다.

dialog --menu <text> <height> <width> <menu-height> [<tag><item>]

선택 항목이 로 전송됩니다 stderr. 다음은 3가지 색상을 사용하는 샘플 스크립트입니다.

#!/bin/bash
TMPFILE=$(mktemp)

dialog --menu "Choose one:" 10 30 3 \
    1 Red \
    2 Green \
    3 Blue 2>$TMPFILE

RESULT=$(cat $TMPFILE)

case $RESULT in
    1) echo "Red";;
    2) echo "Green";;
    3) echo "Blue";;
    *) echo "Unknown color";;
esac

rm $TMPFILE

dialog데비안에서는 다음을 통해 설치할 수 있습니다.같은 이름의 가방.

답변3

질문에는 단 하나의 선택이 포함됩니다.
다중 선택 메뉴를 찾고 있다면 여기로순수한 배쉬구현:

다중 선택 bash 기능 미리보기


j/ k또는 / 화살표 키를 사용하여 위 또는 아래로 이동
(스페이스)하여 선택 항목을 전환하고
(Enter) 선택을 확인합니다.

다음과 같이 호출할 수 있습니다.

my_options=(   "Option 1"  "Option 2"  "Option 3" )
preselection=( "true"      "true"      "false"    )

multiselect result my_options preselection

함수의 마지막 매개변수 multiselect는 선택사항이며 특정 옵션을 미리 선택하는 데 사용할 수 있습니다.

결과는 첫 번째 인수로 전달된 변수에 배열로 저장됩니다 multiselect. 다음은 옵션과 결과를 결합하는 예입니다.

idx=0
for option in "${my_options[@]}"; do
    echo -e "$option\t=> ${result[idx]}"
    ((idx++))
done

다중 선택 bash 기능을 위한 결과 핸들러

function 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#*[}; }

    local return_value=$1
    local -n options=$2
    local -n defaults=$3

    local selected=()
    for ((i=0; i<${#options[@]}; i++)); do
        if [[ ${defaults[i]} = "true" ]]; then
            selected+=("true")
        else
            selected+=("false")
        fi
        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

    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 = "k" ]]; then echo up; fi;
        if [[ $key = "j" ]]; then echo down; fi;
        if [[ $key = $'\x1b' ]]; then
            read -rsn2 key
            if [[ $key = [A || $key = k ]]; then echo up;    fi;
            if [[ $key = [B || $key = j ]]; then echo down;  fi;
        fi 
    }

    toggle_option() {
        local option=$1
        if [[ ${selected[option]} == true ]]; then
            selected[option]=false
        else
            selected[option]=true
        fi
    }

    print_options() {
        # print options by overwriting the last lines
        local idx=0
        for option in "${options[@]}"; do
            local prefix="[ ]"
            if [[ ${selected[idx]} == true ]]; then
              prefix="[\e[38;5;46m✔\e[0m]"
            fi

            cursor_to $(($startrow + $idx))
            if [ $idx -eq $1 ]; then
                print_active "$option" "$prefix"
            else
                print_inactive "$option" "$prefix"
            fi
            ((idx++))
        done
    }

    local active=0
    while true; do
        print_options $active

        # user key control
        case `key_input` in
            space)  toggle_option $active;;
            enter)  print_options -1; 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 $return_value='("${selected[@]}")'
}

신용 거래: 이 bash 기능은 맞춤형 버전입니다.Denis Semenenko의 구현.

답변4

대화형 쉘 메뉴를 구축하기 위한 몇 가지 훌륭한 솔루션이 있습니다. 특히 @miu 및 @alexanderklimitschek의 작업이 있습니다. 비슷한 것을 찾고 있지만 코드가 약간 적고 ZSH에서 작동해야 합니다( ZSH shebang 사용 #!/usr/bin/env zsh). 게다가 나는 처럼되고 싶지 않습니다 dialog.

하지만 여기와 비슷한 사이트에 있는 정말 멋진 스크립트는 모두순수한 배쉬. 이로 인해 배열 인덱싱, 이스케이프 시퀀스 Enter및 기타 키나 내장 명령 read의 차이로 인해 ZSH와 호환되지 않습니다 . 따라서 ZSH용으로 직접 작성해야 했습니다. @Guss 사용자의 간단한 bash 방법을 적용했습니다.우분투에 물어보세요ZSH에 맞게 조정되었습니다. 아마도 순수 ZSH 스크립트에 대해 비슷한 요구 사항을 가진 사람도 이를 사용할 수 있을 것입니다.

#!/usr/bin/env zsh

############################################################################
# zsh script which offers interactive selection menu
#
# based on the answer by Guss on https://askubuntu.com/a/1386907/1771279

function choose_from_menu() {
    local prompt="$1" outvar="$2"
    shift
    shift
    # count had to be assigned the pure number of arguments
    local options=("$@") cur=1 count=$# index=0
    local esc=$(echo -en "\033") # cache ESC as test doesn't allow esc codes
    echo -n "$prompt\n\n"
    # measure the rows of the menu, needed for erasing those rows when moving
    # the selection
    menu_rows=$#
    total_rows=$(($menu_rows + 1))
    while true
    do
        index=1 
        for o in "${options[@]}"
        do
            if [[ "$index" == "$cur" ]]
            then echo -e " \033[38;5;41m>\033[0m\033[38;5;35m$o\033[0m" # mark & highlight the current option
            else echo "  $o"
            fi
            index=$(( $index + 1 ))
        done
        printf "\n"
        # set mark for cursor
        printf "\033[s"
        # read in pressed key (differs from bash read syntax)
        read -s -r -k key
        if [[ $key == k ]] # move up
        then cur=$(( $cur - 1 ))
            [ "$cur" -lt 1 ] && cur=1 # make sure to not move out of selections scope
        elif [[ $key == j ]] # move down
        then cur=$(( $cur + 1 ))
            [ "$cur" -gt $count ] && cur=$count # make sure to not move out of selections scope
        elif [[ "${key}" == $'\n' || $key == '' ]] # zsh inserts newline, \n, for enter - ENTER
        then break
        fi
        # move back to saved cursor position
        printf "\033[u"
        # erase all lines of selections to build them again with new positioning
        for ((i = 0; i < $total_rows; i++)); do
            printf "\033[2k\r"
            printf "\033[F"
        done
    done
    # pass choosen selection to main body of script
    eval $outvar="'${options[$cur]}'"
}
# explicitly declare selections array makes it safer
declare -a selections
selections=(
"Selection A"
"Selection B"
"Selection C"
"Selection D"
"Selection E"
)

# call function with arguments: 
# $1: Prompt text. newline characters are possible
# $2: Name of variable which contains the selected choice
# $3: Pass all selections to the function
choose_from_menu "Please make a choice:" selected_choice "${selections[@]}"
echo "Selected choice: $selected_choice"

여기에 작은 데모가 있습니다. 및를 사용하여 행으로 이동 j하고 k다음을 사용하여 옵션을 선택합니다 Enter.

zsh 메뉴 데모

관련 정보