Leafee98's Blog

TTY 中的回车和换行

· 977 words · 2 minutes to read

常说 Linux 下换行符为 <LF> 而 Windows 下换行符为 <CR><LF>,即便是 Linux 下,TTY 也有对于换行符的额外处理。

在上个世纪,TTY 所模拟的物理终端,其原型打字机,是真的有回车和换行两个按键的,所以在 TTY 输出时,即便本应使用 <LF> 作为换行符,也为了模拟打字机的逻辑,将换行符输出为 <CR><LF>。回车和换行的行为可以简单总结为以下。

<CR> <LF>
常用转义表示 \r \n
常用名称 Carriage Return / 回车 Newline / Linefeed / 换行
ASCII 序号 0x0D 0x0A
其他输入方式 Ctrl + M Ctrl + J

对于 <CR> 的输入,使用键盘的 Enter 键即可,也可以使用 Ctrl + M 输入,但是对于 <LF> ,由于键盘上不再有这个键,所以只能使用 Ctrl + J 输入。

Linux Kernal 的 Terminal Driver 工作在 canonical(也叫 cooked)模式下,此模式会在输入时将 <CR> 转换为 <LF>,在输出时将 <LF> 转换为 <CR><LF>

感受这个现象 🔗

首先创建一个交互式程序 interactive.sh ,它只询问谁在这里,并打一个招呼:

#!/usr/bin/env bash
# with name interactive.sh

echo "Who's here?"
echo -n "> "
read name

echo "Oh hello, $name"

然后创建一个 expect 脚本 interactive.exp,用来代替人工和刚刚的交互式程序进行交互,它会启动该交互程序,并在交互式程序输出“Who’s here”的时候输入“Normal User\n”,最后在交互式程序退出时结束:

#!/usr/bin/expect -f

# exp_internal 1        # Uncomment this for debug output
spawn "./interactive.sh"

expect {
    "Who's here?\r" {               # Must end with "\r" or "\r\n" or neither
        exp_send "Normal User\n" ;  # Can end with either "\r" or "\n"
        exp_continue;
    }
    eof
}

在 expect 脚本中,在期望“Who’s here?”的输出时,如果要匹配换行符,则需要使用 \r\r\n 来匹配部分或全部换行符,如果启用 interactive.exp 中的 debug 输出,你将能够看到下面这样一行,进而得知实际进行匹配的文本是 Who's here?\r\n> ,对于这个换行符为 \r\n 的文本自然也只能用 \r\r\n 来匹配。

expect: does "Who's here?\r\n> " (spawn_id exp4) match glob pattern "Who's here?\r"? yes

TTY 转换换行符在 interactive.exp 中的体现 🔗

匹配 interactive.sh 的输出时,反直觉的一点就是不能够使用 \n 来匹配换行符,推测是因为 expect 虚拟化了一个终端但是没有进行针对换行符的处理,再加上 TTY 在输出时将 \n 转换为 \r\n(interactive.sh 输出的换行符是 \n,这里不再详细考证),于是 expect 就接收到了 \r\n,在脚本中也只能使用 \r\n 来匹配。

而在向 interactive.sh 提供输入内容时,换行符的输入可以任意使用 \r\n,是因为 TTY 在输入时将 \r 转换为 \n,所以无论如何最后输入给程序的一定是 \n,再加上 bash 的 read 的默的 dlimiter 是 $IFS \t\n(空格、制表、换行),所以能够被 read 正确识别到 \n 作为终止符,进而读取到 Normal User 的字符串。

参考 🔗

  1. https://stackoverflow.com/questions/26187170/difference-between-n-and-r-in-expect
  2. https://superuser.com/questions/714078/wrong-newline-character-over-serial-port-cr-instead-of-lf
  3. https://en.wikipedia.org/wiki/Newline
  4. https://en.wikipedia.org/wiki/Carriage_return

Categories