쉘: 읽기: EOF와 개행 문자를 구별합니다.

쉘: 읽기: EOF와 개행 문자를 구별합니다.

단일 문자 읽기, null <EOF>\n? 를 구별하는 방법은 무엇입니까?

예를 들어:

f() { read -rn 1 -p "Enter a character: " char &&
      printf "\nYou entered '%s'\n" "$char"; }

인쇄 가능한 문자 포함:

$ f
Enter a character: x
You entered 'x'

누를 때 Enter:

$ f
Enter a character: 

You entered ''

Ctrl+를 누를 때 D:

$ f
Enter a character: ^D
You entered ''
$ 

마지막 두 경우의 출력이 동일한 이유는 무엇입니까? 어떻게 구별할 수 있나요?

POSIX 쉘과 비교하여 다른 접근 방식이 있습니까 bash?

답변1

(POSIX 기능이 아님) stdin read -n "$n"이 터미널 장치 인 경우 터미널을 해당 모드 read에서 꺼냅니다 . 그렇지 않으면 터미널 줄 규칙의 내부 줄 편집기에서 반환된 전체 줄만 확인한 다음 한 번에 한 바이트씩 읽습니다 . 문자 또는 개행 문자를 읽었습니다(잘못된 문자를 입력하면 예상치 못한 결과가 나타날 수 있습니다).icanonread$n

$n한 줄에서 최대 한 문자를 읽습니다. 또한 $IFS입력에서 IFS 문자가 제거되는 것을 방지하려면 이를 지워야 합니다 .

icanon이제 우리는 패턴 에서 벗어났기 때문에 ^D더 이상 특별하지 않습니다. 그래서 누르면 Ctrl+D문자가 읽혀집니다.^D

어떤 방식으로든 터미널 연결이 끊어지지 않는 한 터미널 장치에서 eof를 볼 수 없습니다. stdin이 다른 유형의 파일인 경우 eof가 표시될 수 있습니다(예: : | IFS= read -rn 1; echo "$?"stdin이 빈 파이프이거나 stdin이 에서 리디렉션됨 /dev/null).

read$n문자(유효한 문자의 일부를 구성하지 않는 바이트는 1문자로 계산됨) 또는 전체 행을 읽은 경우 0이 반환됩니다.

따라서 하나의 문자만 요청하는 특별한 경우에는 다음과 같습니다.

if IFS= read -rn 1 var; then
  if [ "${#var}" -eq 0 ]; then
    echo an empty line was read
  else
    printf %s "${#var} character "
    (export LC_ALL=C; printf '%s\n' "made of ${#var} byte(s) was read")
  fi
else
  echo "EOF found"
fi

POSIXly는 수행하기가 매우 복잡합니다.

이는 다음과 같습니다(EBCDIC가 아닌 ASCII 기반 시스템을 가정).

readk() {
  REPLY= ret=1
  if [ -t 0 ]; then
    saved_settings=$(stty -g)
    stty -icanon min 1 time 0 icrnl
  fi
  while true; do
    code=$(dd bs=1 count=1 2> /dev/null | od -An -vto1 | tr -cd 0-7)
    [ -n "$code" ] || break
    case $code in
      000 | 012) ret=0; break;; # can't store NUL in variable anyway
      (*) REPLY=$REPLY$(printf "\\$code");;
    esac
    if expr " $REPLY" : ' .' > /dev/null; then
      ret=0
      break
    fi
  done
  if [ -t 0 ]; then
    stty "$saved_settings"
  fi
  return "$ret"
}

전체 문자를 읽은 후에만 반환된다는 점에 유의하세요. 잘못된 인코딩(로케일 인코딩과 다름)을 입력한 경우, 예를 들어 터미널이 éiso8859-1(0xe9) 인코딩을 전송하고 UTF-8(0xc3 0xa9)을 예상하는 경우 임의의 숫자 내용을 입력할 수 있지만 é함수는 반품. bashread -n1두 번째 0xe9를 반환하고 둘 다 변수에 저장하는데 이는 약간 더 나은 동작입니다.

(스크립트를 종료하는 대신, , ...에서도 작동) 또는 / on (흐름 제어 대신) ^C에서 문자를 읽으려는 경우 줄 에 a를 추가 할 수 있습니다. 에서도 이 작업을 수행 하지 않는다는 점에 유의하세요 ( 다운되면 다시 시작될 수도 있음).Ctrl+C^Z^\^S^QCtrl+S/Q-isig -ixonsttybashread -n1isig

스크립트가 종료되면(예를 들어 누르는 경우) tty 설정이 복원되지 않습니다 Ctrl+C. 을 추가할 수 있지만 이로 인해 스크립트의 다른 설정이 trap재정의될 수 있습니다 .trap

zsh대신 bashwhere read -k(before ksh93또는 bash's )를 사용할 수도 있습니다 read -n/-N. 이는 터미널에서 문자를 읽고 ^D직접 처리하고(문자가 입력되면 0이 아닌 값을 반환함) 개행 문자를 특별히 처리하지 않습니다.

if read -k k; then
  printf '1 character entered: %q\n' $k
fi

답변2

다음으로 변경 f()하세요 .%s%q

f() { read -rn 1 -p "Enter a character: " char && \
      printf "\nYou entered '%q'\n" "$char"; }
f;f

사용자가 입력하면 출력새로운 팀, 그 다음에'Ctrl-D':

Enter a character: 

You entered ''''
Enter a character: ^D
You entered '$'\004''

`man printf에서:

 %q       ARGUMENT is printed in a format that can be reused as shell input, 
          escaping non-printable characters with the proposed POSIX $'' syntax.

답변3

실제로 read -rn1Bash에서 실행하고 을 클릭 하면 ^DEOF 조건이 아닌 리터럴 제어 문자로 처리됩니다. 제어 문자는 인쇄 시 보이지 않으므로 와 함께 표시되지 않습니다 printf "'%s'". 출력을 비슷한 것에 연결하면 이미 언급된 다른 답변 od -c과 같이 표시됩니다 .printf "%q"

실제로 입력이 없으면 결과가 다릅니다. 다음을 사용하더라도 여기서는 비어 있습니다 printf "%q".

$ f()  { read -rn 1  x ; printf "%q\n" "$x"; }
$ printf "" | f
''

여기서 개행 문자가 반환되지 않는 데에는 두 가지 이유가 있습니다 read. 첫째, 읽기를 위한 기본 줄 구분 기호이므로 출력으로 반환됩니다. 둘째, 이는 기본값의 일부이기도 하며 IFSread일부인 경우 선행 및 후행 공백을 제거합니다 IFS.

read -d따라서 기본 구분 기호를 변경 해야 합니다 .그리고분명한 IFS:

$ g() { IFS= read -rn 1 -d '' x ; printf "%q\n" "$x"; }
$ printf "\n" | g
$'\n'

read -d ""구분 기호를 효과적으로 NUL 바이트로 만듭니다. 이는 여전히 입력 없음과 NUL 바이트 입력 간의 차이를 알 수 없음을 의미합니다.

$ printf "" | g
''
$ printf "\000" | g
''

입력이 없더라도 readfalse가 반환되므로 $?이를 감지할 수 있는지 확인할 수 있습니다.

답변4

read -r var
status=$?
echo "\$var='$var':\$?=$status"

개행 및 Ctrl-D 케이스는 상태 변수로 구별됩니다.

줄 바꿈 문자인 경우 상태는 true(0)이고, Ctrl-D를 누르면 상태는 false(1)입니다.

관련 정보