为什么 Enter 键不发送 EOL?

为什么 Enter 键不发送 EOL?

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

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

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

答案1

本质上“因为自从手动打字机出现以来就一直是这样做的”。真的。

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

随着电子数据输入(电传打字机等)的引入,他们也将其发扬光大。因此,Enter许多终端上的按键都标有Return

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

这两个操作都经过编码(以允许电传打字机不仅仅是创建纸张类型的独立设备),因此我们有CR(回车)和LF(换行)。这张图片来自ASR 33 电传打字机信息显示键盘, 位于Return右侧,Line-Feed位于左侧。正处于正确的,这是主键:

在此输入图像描述

后来Unix出现了。它的开发人员喜欢缩短内容(看看所有的缩写,甚至creat是“创建”)。面对可能由两部分组成的过程,他们认为换行符只有在换行符前面有回车符才有意义。所以他们放弃了显式的回车符文件,并翻译终端的Return按键以发送相应的换行符。为了避免混淆,他们将换行称为“换行”。

当在终端上写入文本时,Unix 会进行相反的转换:换行变成回车/换行。

(即“正常”:所谓的“熟模式”,与不进行翻译的“原始”模式相反)。

概括:

  • 回车/换行的顺序是 13 10
  • 设备发送 13(因为用你们的话来说是“永远”)
  • 类Unix系统改变到 13 10
  • 其他系统不一定只存储 10(Windows 基本上只接受 10 或 13 10,具体取决于兼容性的重要性)。

答案2

尽管托马斯·迪基的回答非常正确,Stéphane Chazelas 在对 Dickey 的回答的评论中正确地提到,转换并不是一成不变的;这是生产线纪律的一部分。

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

男人 3 术语手册页基本上包含所有相关信息。 (该链接需要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 或单一 Unix 规范系统。)
  • ONOCR:跳过(不输出)第一列的CR。
  • ONLRET:跳过(不输出)CR。

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

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

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

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

    new_settings[0] = new_settings[0] & ~termios.INLCR
    new_settings[0] = new_settings[0] & ~termios.ICRNL
    new_settings[0] = new_settings[0] & ~termios.IGNCR

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

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

最后,当设置新属性时,如果满足以下条件,调用将成功:任何的新设置已设置。如果设置很敏感(例如,如果您在命令行上要求输入密码),则应该获取新设置,并确认重要标志是否正确设置/取消设置。

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

stty -a

输入标志通常位于第四行,输出标志位于第五行,-如果未设置标志,则在标志名称之前添加标志名称。例如,输出可以是

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

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

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

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

EXIT每当 shell 退出时都会执行陷阱。stty -g在脚本开始时读取终端的当前设置,因此当脚本退出时会自动恢复当前设置。您甚至可以使用Ctrl+中断脚本C,它会做正确的事情。 (在一些带有信号的极端情况下,我发现终端有时会卡在原始/非规范设置(需要在终端上盲目输入reset+ ),但在恢复实际原始设置之前运行已经解决了每次这就是为什么它存在的原因;)Enterstty sane

您可以使用内置的 bash 读取输入行(未回显到终端)read,甚至可以使用以下命令逐个字符地读取输入

IFS=$'\0'
input=""
while read -N 1 c ; do
    [[ "$c" == "" || "$c" == $'\n' || "$c" == $'\r' ]] && break
    input="$input$c"
done

如果不设置IFS为 ASCII NUL,read内置将消耗分隔符,因此c将为空。年轻球员的陷阱。

相关内容