为什么 Enter 键不发送 EOL?

cat*_*cat 19 command-line terminal ascii newlines

Unix / Linux EOL 是 LF、换行、ASCII 10、转义序列\n

这是一个 Python 代码片段,可以准确地获得一个按键:

import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
    tty.setraw(sys.stdin.fileno())
    ch = sys.stdin.read(1)
finally:
    termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
    return ch
Run Code Online (Sandbox Code Playgroud)

当我按下Enter键盘以响应此代码段时,它给出了\r回车符 ASCII 13。

Windows 上Enter发送CR LF == 13 10. *nix 不是 Windows;为什么Enter给 13 而不是 10?

Tho*_*key 30

本质上是“因为自从手动打字机以来它就已经这样做了”。真的。

手动打字机有一个托架,可在其上进纸,并在您打字时向前移动(加载弹簧),并且有一个杠杆或钥匙可以释放托架,让弹簧将托架返回到左边距。

随着电子数据输入(电传打字机等)的引入,他们继续推进。因此Enter,许多终端上的密钥将被标记为Return

在将托架返回到左边距后(在手动过程中)发生换行。再次,电子设备模仿手动设备,进行单独line-feed操作。

这两个操作都被编码(允许电传打字机不仅仅是创建纸张类型的独立设备),所以我们有CR(回车)和LF(换行)。这张来自ASR 33 Teletype Information 的图片显示了键盘,Return在右侧,Line-Feed在左侧。在右边,这是主要的关键:

在此处输入图片说明

Unix 后来出现。它的开发人员喜欢缩短东西(看看所有的缩写,即使creat是“create”)。面对可能由两部分组成的过程,他们决定换行只有在它们前面有回车时才有意义。因此,他们从files 中删除了显式回车,并转换了终端的Return密钥以发送相应的换行符。为了避免混淆,他们将换行称为“换行”。

在终端上写入文本时,Unix 会转换为另一个方向:换行符变为回车符/换行符。

(即,“通常”:所谓的“熟模式”,与不进行翻译的“原始”模式形成对比)。

概括:

  • 回车/换行是序列 13 10
  • 设备发送13(因为“永远”在你的条件)
  • 类 Unix 系统更改为 13 10
  • 其他系统不一定只存储 10(Windows 在很大程度上只接受 10 或 13 10,这取决于兼容性的重要性)。

  • 如果您必须输入其中之一,您也会缩写所有内容! (3认同)
  • 关于历史部分:我使用的手动打字机,类似于[这个](https://reassembleforus2016.files.wordpress.com/2015/11/top.jpg?w=788&h=557)只有一个杠杆。当您拉动它时,它首先转动滚轮(换行)​​,然后它只会拉动托架。正是这种拉力加载了弹簧。每个键入的字母或按下的标签都会稍微释放弹簧,将托架移回“卸载”位置,该位置位于行的末尾,而不是其开头。 (3认同)
  • 在输入时,CR 被(通过 tty 行规则)转换为 LF,而不是 CR LF。在输出(包括输入的回声)时,`LF` 被转换为 `CR LF`。当你在熟模式下输入 `foo<Return>` 时,应用程序读取 `foo\n` 并且 `foo\r\n` 被线路规则送回以回显到终端。 (2认同)
  • 【回车使用视频】(https://www.youtube.com/watch?v=kU09i50pg6o) (2认同)

Nom*_*mal 11

虽然Thomas Dickey 的回答非常正确,但 Stéphane Chazelas 在对 Dickey 的回答的评论中正确地提到转换并非一成不变;它是生产线纪律的一部分。

事实上,翻译是完全可编程的。

男子3周的termios手册页基本上包含所有相关信息。(该链接指向Linux 手册页项目,其中确实提到了哪些功能仅适用于Linux,哪些功能是 POSIX 或其他系统所共有的;请始终检查那里每个页面上的符合部分。)

所述iflag终端属性(old_settings[0]在问题中所示的代码的Python)具有对所有POSIXy系统三个相关国旗:

  • INLCR: 如果设置,则在输入时将 NL 转换为 CR
  • ICRNL: 如果设置(并且IGNCR未设置),则在输入时将 CR 转换为 NL
  • IGNCR: 忽略输入的 CR

同样,也有相关的输出设置(old_settings[1]):

  • OPOST: 启用输出处理。
  • OCRNL:在输出时将 CR 映射到 NL。
  • ONLCR: 在输出时将 NL 映射到 CR。(XSI;并非在所有 POSIX 或 Single-Unix-Specification 系统中都可用。)
  • ONOCR: 跳过(不输出)第一列的 CR。
  • ONLRET: 跳过(不输出)CR。

例如,您可以避免依赖tty模块。“makeraw”操作只是清除一组标志(并设置CS8oflag):

import sys
import termios

fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
ch = None

try:
    new_settings = termios.tcgetattr(fd)
    new_settings[0] = new_settings[0] & ~termios.IGNBRK
    new_settings[0] = new_settings[0] & ~termios.BRKINT
    new_settings[0] = new_settings[0] & ~termios.PARMRK
    new_settings[0] = new_settings[0] & ~termios.ISTRIP
    new_settings[0] = new_settings[0] & ~termios.INLCR
    new_settings[0] = new_settings[0] & ~termios.IGNCR
    new_settings[0] = new_settings[0] & ~termios.ICRNL
    new_settings[0] = new_settings[0] & ~termios.IXON
    new_settings[1] = new_settings[1] & ~termios.OPOST
    new_settings[2] = new_settings[2] & ~termios.CSIZE
    new_settings[2] = new_settings[2] | termios.CS8
    new_settings[2] = new_settings[2] & ~termios.PARENB
    new_settings[3] = new_settings[3] & ~termios.ECHO
    new_settings[3] = new_settings[3] & ~termios.ECHONL
    new_settings[3] = new_settings[3] & ~termios.ICANON
    new_settings[3] = new_settings[3] & ~termios.ISIG
    new_settings[3] = new_settings[3] & ~termios.IEXTEN
    termios.tcsetattr(fd, termios.TCSANOW, new_settings)
finally:
    termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

return ch
Run Code Online (Sandbox Code Playgroud)

尽管出于兼容性考虑,您可能希望首先检查 termios 模块中是否存在所有这些常量(如果您在非 POSIX 系统上运行)。您还可以使用new_settings[6][termios.VMIN]new_settings[6][termios.VTIME]来设置在没有挂起数据的情况下读取是否会阻塞,以及多长时间(以整数分秒为单位)。(通常VMIN设置为 0,VTIME如果读取应立即返回,则设置为 0,或者设置为正数(十分之一秒)读取最多应等待多长时间。)

如您所见,上述(以及一般的“makeraw”)禁用了输入的所有翻译,这解释了 cat 所看到的行为:

    new_settings[0] = new_settings[0] & ~termios.INLCR
    new_settings[0] = new_settings[0] & ~termios.ICRNL
    new_settings[0] = new_settings[0] & ~termios.IGNCR
Run Code Online (Sandbox Code Playgroud)

要获得正常行为,只需省略清除这三行的行,即使在“原始”时输入翻译也不会改变。

new_settings[1] = new_settings[1] & ~termios.OPOST行禁用所有输出处理,不管其他输出标志怎么说。您可以省略它以保持输出处理完整。即使在原始模式下,这也使输出保持“正常”。(它不影响输入是否自动回显;这是由 中的ECHOcflag控制的new_settings[3]。)

最后,当设置新属性时,如果设置了任何新设置,则调用将成功。如果设置是敏感的——例如,如果你在命令行上要求输入密码——你应该获取新设置,并验证重要标志是否正确设置/取消设置,以确保。

如果要查看当前的终端设置,请运行

stty -a
Run Code Online (Sandbox Code Playgroud)

输入标志通常在第四行,输出标志在第五行,-如果未设置标志,则在标志名称前面加上一个。例如,输出可能是

speed 38400 baud; rows 58; columns 205; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?; swtch = M-^?; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 hupcl -cstopb cread -clocal -crtscts
-ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc ixany imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
Run Code Online (Sandbox Code Playgroud)

在伪终端和 USB TTY 设备上,波特率无关紧要。

如果您编写希望读取密码等 Bash 脚本,请考虑以下习惯用法:

#!/bin/bash
trap 'stty sane ; stty '"$(stty -g)" EXIT
stty -echo -echonl -imaxbel -isig -icanon min 1 time 0
Run Code Online (Sandbox Code Playgroud)

EXIT每当外壳退出执行陷阱。在stty -g读取在脚本的开始时的终端的当前设置,所以当前设置当脚本退出时,自动恢复。你甚至可以用Ctrl+中断脚本C,它会做正确的事情。(在某些带有信号的极端情况下,我发现终端有时会卡在原始/非规范设置中(需要在终端上键入reset+Enter盲目),但stty sane在恢复实际原始设置之前运行已经解决了每次我。所以这就是它在那里的原因;一种额外的安全。)

您可以使用read内置的 bash读取输入行(未回显到终端),甚至可以使用

IFS=$'\0'
input=""
while read -N 1 c ; do
    [[ "$c" == "" || "$c" == $'\n' || "$c" == $'\r' ]] && break
    input="$input$c"
done
Run Code Online (Sandbox Code Playgroud)

如果您没有设置IFS为 ASCII NUL,read内置将使用分隔符,因此c它将为空。年轻球员的陷阱。

  • 虽然你愿意放弃你的 +15 代表是你的功劳,但@cat 是完全正确的。答案是否被接受并不表示它是已发布答案中“最正确的”。这仅意味着无论出于何种个人原因,OP 都偏爱那一种。“最正确的”通常是最受好评的。接受答案取决于个人喜好,如果 OP 更喜欢您的答案,则没有理由不接受它。 (4认同)
  • @cat:虽然这可能对您最有帮助,但我仍然会说 Thomas Dickey 的答案*更正确*。我宁愿你接受这一点。 (2认同)