shell:读取:区分 EOF 和换行符

Tom*_*ale 8 shell bash posix

读取单个字符,如何区分 null<EOF>\n?

例如:

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

带有可打印字符:

$ f
Enter a character: x
You entered 'x'
Run Code Online (Sandbox Code Playgroud)

按下时Enter

$ f
Enter a character: 

You entered ''
Run Code Online (Sandbox Code Playgroud)

Ctrl+ 时D

$ f
Enter a character: ^D
You entered ''
$ 
Run Code Online (Sandbox Code Playgroud)

为什么在最后两种情况下输出相同?我如何区分它们?

在 POSIX shell 和 POSIX shell 中是否有不同的方法来做到这一点bash

Sté*_*las 12

使用read -n "$n"(不是 POSIX 功能),并且如果 stdin 是终端设备,read则将终端置于icanon模式之外,否则read只会看到终端行规则内部行编辑器返回的完整行,然后一次读取一个字节,直到$n已读取字符或换行符(如果输入无效字符,您可能会看到意外结果)。

$n从一行读取字符。您还需要清空$IFS它以免从输入中去除 IFS 字符。

既然我们离开了icanon模式,^D就不再特别了。因此,如果您按Ctrl+D^D则会读取该字符。

除非终端以某种方式断开连接,否则您不会从终端设备看到 eof。如果 stdin 是另一种类型的文件,您可能会看到 eof (例如在: | IFS= read -rn 1; echo "$?"stdin 是空管道的情况下,或重定向 stdin from /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
Run Code Online (Sandbox Code Playgroud)

用 POSIXly 来做是相当复杂的。

这将类似于(假设一个基于 ASCII(而不是 EBCDIC)的系统):

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"
}
Run Code Online (Sandbox Code Playgroud)

请注意,我们仅在读取完整字符时返回。如果输入错误的编码(根据区域的不同编码),例如,如果您的终端发送é编码为ISO8859-1(0xe9)时,我们预计UTF-8(0xc3 0xa9),那么你可以尽可能多的进入é,你喜欢,函数不会返回。bash'sread -n1将在第二个 0xe9 上返回(并将两者都存储在变量中),这是一个稍微好一点的行为。

如果你也想读^C在字符Ctrl+C(而不是让它杀你的脚本;也^Z^\......),或 ^S/^QCtrl+S/Q(而不是流量控制),你可以添加-isig -ixonstty线。请注意,bash'sread -n1也不会这样做(isig如果它关闭,它甚至会恢复)。

如果脚本被终止,这将不会恢复 tty 设置(例如,如果您按Ctrl+C。您可以添加一个trap,但这可能会覆盖trap脚本中的其他s。

您也可以使用zsh代替bash, where read -k(早于ksh93bash's read -n/-N)从终端读取一个字符并^D自行处理(如果输入该字符则返回非零值)并且不特别处理换行符。

if read -k k; then
  printf '1 character entered: %q\n' $k
fi
Run Code Online (Sandbox Code Playgroud)