키 누름에 반응하는 스크립트를 작성하려면 무엇을 고려해야 합니까?

키 누름에 반응하는 스크립트를 작성하려면 무엇을 고려해야 합니까?

나는 다음과 같은 스크립트에서 변수에 무언가를 읽을 수 있다는 것을 알고 있습니다. 변수 = 읽기 그러나 변수에 값을 제출하려면 Enter 키를 눌러야 합니다. Enter 키를 누르지 않고 키 누르기 값을 변수에 제출하려면, 또는 키를 눌렀을 때 반응하지 않으려면 무엇을 알아야 합니까?

답변1

를 사용하면 내장 함수에 대한 인수를 bash사용하여 개행 없이 읽는 문자 수를 제한할 수 있습니다.-nread

#!/bin/bash

echo "Ready? [Y/n]: "
read -n 1 y_or_n
echo

case "$y_or_n" in
    [Yy]|"")
        echo "you said yes"
        ;;
    *)
        echo "you said no"
        ;;
esac

bashsh이것은 as 또는 호출 여부에 관계없이 작동합니다 bash.

자세한 내용은 help read또는 맨페이지를 참조하십시오.bash

다른 쉘은 -n매개변수를 지원하지 않을 수도 있습니다 read. dash, 예를 들어 아니요.

답변2

셸에 관한 문제는 read총 바이트 수를 제한하든 안 하든 상관없이 read여전히 정보를 제공하지 않는다는 것 입니다.단추각자 read는 일부만 얻을 것이다안정적인수량수치매번 read하나 얻으려고단추필요 에 따라 read입력을 차단하는 것이 아마도 가장 좋은 방법일 것입니다 dd. 다음은 몇 달 전 dd터미널 I/O를 차단하려고 할 때 작성한 쉘 함수입니다.

printkeys()(    IFS= n=1
    set -f -- "$(tty <&2)"
    exec    <${1##[!/]*};set \\t
    _dd()(  c=conv=sync,unblock i=ibs=72 b=bs=8
            dd bs=7 ${c%,*} |   dd $i o$b c$b $c
    )       2>/dev/null
    trap "  trap '' TTOU TTIN
            stty '$(stty -g
            stty raw isig inlcr)'   "   INT EXIT
    _dd |while  ${2:+:} read -r c   ||  exit 2
            do  case $#:$c in (1:): ;; 
        (*:?*)  set ":%d$@"         \
                    \'${c%"${c#?}"} ;
                c=${c#?}            ;; 
        (*)     printf "$@"         ;
        [ 0 -eq $((n=n<4?n+1:0)) ]  &&
                set '\n\r'||set \\t ;;  esac
        done
)

위의 경우 터미널 입력이 dd파이프의 두 프로세스 모두에서 필터링되는 것을 볼 수 있습니다. 터미널이 다음 stty으로 설정되었습니다.날것의의 패턴은 trap오류 발생 시 최종 상태를 복원 INT하거나 EXIT함수가 호출되었을 때 함수의 상태로 복원 할 수도 있습니다.(아마 CTRL+C나 인터럽트 키를 사용하여 수행할 수 있음). 존재하다날것의모드에서 터미널은 입력이 도착하자마자 모든 판독기에게 입력을 플러시합니다(키 입력마다 키 입력). 한 번에 1바이트만 푸시하는 것이 아닙니다. 가능한 한 빨리 전체 버퍼를 푸시합니다. 예를 들어 위쪽 화살표를 누르면 키보드는 다음과 같은 이스케이프 시퀀스를 보냅니다.

^[[A

...3바이트인데 한꺼번에 밀어넣습니다.

dd일단 제공된 모든 읽기를 충족하도록 지정됩니다(설정한 높이에 관계 없음) ibs=. 이는 을 사용한 설정에도 불구하고 ibs=7터미널이 3바이트만 푸시할 때 읽기가 여전히 완료된다는 것을 의미합니다. 이제 대부분의 다른 유틸리티에서는 이를 처리하는 데 문제가 있지만 dd이것이 conv=syncbytes 사이의 차이를 채워줍니다 \0NUL. 따라서 키보드의 위쪽 화살표를 누르면 dd3바이트가 읽히고 다음 바이트에 7이 기록됩니다. 즉 dd, 이스케이프 시퀀스의 3바이트와 또 다른 4 \0NUL바이트입니다.

그러나 쉘의 읽기와 함께 이 데이터를 가져오려면 이를 다시 차단해야 하므로 다음 데이터는 dd입력 버퍼를 72바이트로 동기화합니다. 그러나 실제로는 conv=unblock그렇습니다. unblock변환을 통해 dd입력을 \newline 구분 기호 로 분할하여 cbs=개수를 얻습니다. 여기서는 8입니다 sync.unblock (또는 block)변환은 s에서는 동기화 dd되지 않지만 \0NUL후행 공백에서는 동기화됩니다. 따라서 7바이트마다 첫 번째는 dd두 버퍼 사이의 파이프에 dd72바이트를 씁니다. 처음 몇 바이트는 키의 내용이고 그 다음은 \0NULs, 그 다음에는 65개의 공백 읽기의 끝입니다.

또 다른 점은 후행 공백을 제거하는 것입니다. 이는 각 변환 블록 unblock끝에 존재할 수 있는 만큼의 공백을 차지하게 됩니다. cbs=따라서 각 dd쓰기는 바이트 단위로 출력에 쓰므 obs=8로 각 읽기는 9줄을 쓰므로 총 2번의 쓰기가 출력 파이프에 기록됩니다. 첫 번째 쓰기는 입력 파이프에서 읽은 7바이트와 후행 개행 문자(또 다른 바이트)로 구성된 첫 번째 줄입니다. 다음 쓰기는 8개의 개행(연속 쓰기)입니다. 8바이트가 더 많습니다. 왜냐하면 dd8개의 변환 블록 각각이 8개의 공백을 모두 차지하기 때문입니다.

dd두 sa 쉘의 반대쪽을 while한 줄씩 반복합니다 read. 빈 줄을 무시할 수 있습니다. 왜냐하면 터미널은 옵션에 따라 모든 줄 바꿈을 출력의 캐리지 리턴으로 변환하기 때문입니다 inlcr stty. 그러나 $c쉘의 입력이 해당 값을 입력한 후 단일 바이트라도 감지 하면 read키보드가 키 입력당 많은 바이트를 전송하지 않는 한 키 누르기와 전체 키 누르기도 가능합니다. 7바이트 이상(단지 다른 차단 요소가 필요하지만).

셸에 키 누르기가 있으면 $c바이트 단위로 반복하고 '문자로 구분된 배열로 분할한 다음 printf각 바이트의 10진수 값을 한 번에 가져옵니다. $c함수를 실행하면 다음과 같은 출력이 표시됩니다.

a:97    b:98    c:99    d:100   e:101
f:102   ;:59    ^M:13   ^M:13   ^M:13
s:115   a:97    d:100   f:102    :32
':39    ':39    ':39    a:97    s:115
d:100   f:102   ;:59    ':39    ^[[A:27:91:65
^[[D:27:91:68   ^[[B:27:91:66   ^[[C:27:91:67   ^[[D:27:91:68   ^[[C:27:91:67
^[[A:27:91:65   ^[[D:27:91:68   ^[[C:27:91:67   ^[[B:27:91:66   ^[[D:27:91:68
^[[C:27:91:67   ^[[A:27:91:65   ^[[D:27:91:68   ^[[C:27:91:67   ^[[B:27:91:66

쉘이 변수의 값을 입력으로 채우면 키 누름을 방지하기 위해 삽입된 s가 \0NUL사라지기 때문에 s를 쉘 변수에 넣을 수 없습니다.dd\0NUL(그러나 어떤 쉘에서든 zsh- 이 경우에는 여전히 다음과 같이 구성할 수 있습니다). 내가 아는 한, 이것은 모든 쉘에서 작동해야 하며 , 및 에서도 bash작동 dash합니다 ksh93. 멀티바이트 입력도 안정적으로 처리합니다. 하지만 그렇다고 장담할 수는 없습니다.

위의 데모 출력에서는 작성될 때마다 함수의 실제 출력 앞에 작성되지 않은 추가 정보가 표시됩니다. :위의 순서에서 처음으로 나타나기 전에 표시되는 각 문자는 실제로 터미널 문자이며 사용 하거나 구성 echo할 수 있습니다 . 나머지는 함수의 출력으로 인쇄됩니다. 보시다시피 입력 직후에 인쇄됩니다.stty echo-echo

관련 정보