사용자 인터페이스_05 사용자 키 입력을 한 글자만 받기(Enter 키 불필요)

 
  • 출처 : 유닉스 리눅스 쉘스크립트 예제사전_한빛미디어

명령어: stty, case, dd
키워드: 키보드, 입력, 줄바꿈, Enter
사용처: 키보드에서 글자 하나가 입력되면 Enter입력 없이 처리를 속행하고 싶을 때


실행예제

$ ./getchar.sh
Type Your Answer [y/n] : y
Input : Yes

스크립트

#!/bin/sh

echo -n "Type Your Answer [y/n]: "

# 현재 터미널 설정을 셸 변수 tty_state에 백업하고
# 터미널을 raw 설정함
tty_state=$(stty -g)  #--------------------- 1
stty raw  #--------------------------------- 2

# 키보드에서 문자 하나 읽기
char=$(dd bs=1 count=1 2> /dev/null)  #----- 3

# 터미널 설정을 원래대로 돌림
stty "$tty_state"  #------------------------ 4

echo

# 입력된 문자에 따라 처리 분기
case "$char" in
  [y/Y])
    echo "Input: YES"
    ;;
  [n/N])
    echo "Input: NO"
    ;;
  *)
    echo "Input: What?"
    ;;
esac

   

해설

이 스크립트는 사용자에게 Yes/No를 물어서 입력된 문자로 Yes인지 No인지 판단해서 메시지를 표시합니다.

스크립트 실행 중에 계속 진행할지 여부를 확인하는 등 사용자에게 무언가를 입력받고 싶을 때 read 명령어를 써서 키 입력을 얻을 수 있는데 read 명령어는 반드시 줄바꿈이 필요합니다. 즉, 사용자는 문자를 입력한 다음 Enter 키를 눌러야만 합니다.

하지만 단순히 Yes/No를 묻는다면 Enter 키를 누르지 않고 y 또는 n을 누르면 처리를 진행하고 싶습니다.이럴 때는 터미널(단말) 상태를 설정하는 stty 명령어로 터미널을 raw 모드로 하면 키 버퍼 처리를 하지 않으므로 문자마다 처리할 수 있습니다.

이 예제에서 줄 바꿈이 없는 메시지를 echo -n으로 표시한 다음, 현재 터미널 설정을 stty -g 명령어 출력 결과를 가지고 저장합니다1. stty -g 출력은 다음과 같으며 현재 터미널 설정이 저장됩니다.

  • 현재 터미널 설정 표시

    $ stty -g
    2d00:5:bf:ca3b:3:1c:7f:15:4:0:1:0:11:13:1a:ff:12:f:17:16:ff:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0
    

이후 raw 모드로 할 때 현재 터미널 설정이 지워지므로 나중에 설정을 원래대로 돌릴 수 있도록 현재 설정을 일시적으로 셸 변수 tty_state에 저장합니다.

이어서 2에서 stty raw 명령어로 터미널을 raw 모드로 전환합니다. 이러면 실제 키 입력값은 다음 dd 명령어에 직접 전달됩니다. 3의 dd 명령어에서는 입출력 블록 크기를 1(bs=1), 입력에서 출력으로 복사하는 블록 수를 1(count=1)로 해서 입력된 문자를 셀 변수 char에 설정합니다.

또한, dd 명령어 실행 메시지는 필요 없으므로 /dev/null로 리다이렉트해서 버립니다. 이대로는 터미널이 raw 모드이므로 스크립트 실행 이전 상태로 되돌려야 합니다. 4에서 stty 명령어를 써서 stty “$tty_state”로 지정하면 사용자 터미널 상태는 raw 모드를 벗어나서 처음에 저장한 터미널 상태로 돌아갑니다. 입력된 값은 셸 변수 char에 들어있으므로 5에서 case 분기합니다. 입력된 문자가 y 또는 Y라면 Yes 처리, n 또는 N이라면 No 처리를 합니다.

   

Mac에서 echo 명령어

Mac에서 echo 명령어는 조금 성가십니다. Mac에서 echo 명령어는 명령행에서 직접 사용할 때는 줄바꾸지 않는 -n 옵션이 동작합니다. 하지만 셸 스크립트 내부에서 echo -n은 옵션으로 지정한 “-n”이 문자열로 그대로 출력됩니다.

이것은 echo 명령어가 셸의 내부 명령어로 실행되는 것이 원인입니다. 셸 스크립트에서 이용하는 명령어로는 ‘외부 명령어’와 ‘셸 내부 명령어’ 두 종류가 있습니다. 외부 명령어는 /bin/echo 처럼 실행 파일이 존재합니다. 한편, 셸 내부 명령어는 셸 자체 내부에 있는 명령어로 실행 파일이 존재하지 않습니다.

이 예제처럼 echo를 쓰면 셸 내부의 echo 명령어가 사용됩니다. Mac에서는 기본 로그인 셀이 bash이므로 명령행에서는 bash의 셸 내부 echo 명령어가 실행됩니다. 이것은 -n에 대응하므로 원하는 대로 줄바꿈이 없게 실행됩니다.

한편, 스크립트 내부에 echo를 쓰면 bash가 아니라 sh 셸 내부인 echo 명령어가 실행됩니다. Mac의 sh 셸 내부 echo 명령어는 옵션 -n에 대응하지 않으므로 지정해도 인수로 해석되어서 “-n”이 그대로 출력됩니다. 따라서 Mac에서 개행하지 않는 메시지를 echo로 출력하려면 다음처럼 외부 명령어 echo를 사용합니다.

/bin/echo -n "Type Toyr Answer [y/n] : "

또는 다음처럼 printf 명령어를 쓰는 방법도 있습니다. printf 명령어는 \n으로 지정하지 않으면 줄바꾸지 않으므로 그대로 출력됩니다.

printf "Type Your Answer [y/n] : "