为什么Enter键不发送EOL?


19

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。

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


尝试读取两个字节。
迈克尔·汉普顿

@MichaelHampton不,没有什么等待这个文件描述符读取一个字节后

Answers:


11

尽管托马斯·迪基的答案很正确,但斯特凡·查泽拉斯在对迪基的答案的评论中正确地提到,这种转变不是一成不变的。它是生产线学科的一部分。

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

男子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”操作仅清除一组标志(并设置CS8offlag):

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”)会禁用输入的所有翻译,这说明了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

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

new_settings[1] = new_settings[1] & ~termios.OPOST行将禁用所有输出处理,无论其他输出标志怎么说。您可以忽略它以保持输出处理完好无损。这样即使在原始模式下也可以保持输出“正常”。(它不会影响是否自动回显输入;这由中的ECHOcflag 控制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每当外壳退出执行陷阱。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

如果未设置IFS为ASCII NUL,则read内置函数将使用分隔符,因此c将为空。年轻球员的陷阱。


1
哦,对神的缘故,没有什么以往任何时候都简单:(

我接受这个答案,因为它对我作为Python开发人员最有帮助,尽管另一个人很棒
cat

2
@cat:虽然这可能对您最有帮助,但我仍然会说Thomas Dickey的答案更正确。我希望您改为接受。
名义动物

4
虽然您愿意放弃+15名代表,但@cat是正确的。答案是否被接受并不表示它是已发布答案中“最正确”的。这仅表示这是OP出于个人原因而首选的选项。“最正确的”通常是最受批评的。接受答案取决于个人喜好,如果OP偏爱您的答案,则没有理由不接受。
terdon

1
@terdon:好的,那我就纠正了。
名义动物

30

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

手动打字机有一个供纸盘送入的滑架,在您打字时(装入弹簧)它向前移动,并且有一个可以松开滑架的杠杆或钥匙,使弹簧使滑架回到左边缘。

随着电子数据录入(电传等)的引入,他们将其向前推进。因此Enter,许多终端上的键都将被标记Return

在将滑架返回左边缘后,发生了换行(在手动过程中)。同样,电子设备模仿手动设备,进行单独的line-feed操作。

两种操作都经过编码(以使电传打字本不只是用于创建纸张类型的独立设备),因此我们有CR(回车)和LF(换行)。ASR 33 Teletype Information的这张图片显示了键盘,Return在右侧和Line-Feed在左侧。在右边,这是主要的关键:

在此处输入图片说明

Unix后来出现了。它的开发人员喜欢缩短内容(即使creat是“ create” ,也要查看所有缩写)。面对可能分为两部分的过程,他们决定只有在回车之前加换行才有意义。因此,他们从文件中删除了明确的回车符,并转换了终端的Return密钥以发送相应的换行符。为了避免混淆,他们将换行称为“换行符”。

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

(也就是说,“正常”:所谓的“烹饪模式”,与未进行任何翻译的“原始”模式相反)。

摘要:

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

1
我寻找一张漂亮的图片来展示手动打字机的操纵杆,但只发现了低分辨率的图像。
Thomas Dickey

3
如果您必须输入其中之一,您也将全部缩写!
迈克尔·汉普顿

3
关于历史的一部分:我在使用中使用的手动打字机,类似这样一个只有一杆。当您拉动它时,它首先摇动滚轮(换行)​​,然后才将其滑入。正是这种拉力加载了弹簧。每个键入的字母或按下的制表符都会稍微松开弹簧,将滑架移回“卸载”位置,该位置位于行的末尾,而不是行的起点。
RealSkeptic

2
在输入时,CR(由tty行准则)转换为LF,而不是CR LF。它LF 位于翻译成的输出(包括输入的回声)上CR LF。当您输入foo<Return>熟模式时,该应用程序将读取内容foo\nfoo\r\n通过线路规程发送回去,以回显到终端。
斯特凡Chazelas

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.