new essays: carriage-return-and-linefeed-on-tty
All checks were successful
ci/woodpecker/push/deploy Pipeline was successful
All checks were successful
ci/woodpecker/push/deploy Pipeline was successful
This commit is contained in:
parent
707daff261
commit
8761fb8207
78
content/essays/carriage-return-and-linefeed-on-tty.md
Normal file
78
content/essays/carriage-return-and-linefeed-on-tty.md
Normal file
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
title: "TTY 中的回车和换行"
|
||||
date: 2023-08-29T16:48:34+08:00
|
||||
tags: []
|
||||
categories: []
|
||||
weight: 50
|
||||
show_comments: true
|
||||
draft: false
|
||||
description: "在 TTY 中,输入时 <CR> 会转换为 <LF>,输出时 <LF> 会转换为 <CR><LF>"
|
||||
---
|
||||
|
||||
常说 Linux 下换行符为 `<LF>` 而 Windows 下换行符为 `<CR><LF>`,即便是 Linux 下,TTY 也有对于换行符的额外处理。
|
||||
|
||||
<!--more-->
|
||||
|
||||
在上个世纪,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 ,它只询问谁在这里,并打一个招呼:
|
||||
|
||||
```bash
|
||||
#!/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>
|
Loading…
Reference in a new issue