您遇到的问题是“原始”、“熟悉”和“cbreak”模式之间的差异。这些模式是内核级终端驱动程序的模式,而不是应用程序代码、标准库或用户空间中的任何其他模式。这是老派Unix称呼它们的方式。Posix已经用一组更精细的属性替换了它们,尽管Posix属性通常与辅助函数一起翻转,以模仿旧的“原始”、“熟悉”和“cbreak”模式。
在熟悉模式下,终端驱动程序本身具有基本的行编辑功能。它处理退格、单词删除(基本上是一次性删除整个单词)和类似的操作。没有像处理箭头键或历史记录等复杂的功能。在此模式下,直到发送行结束符(eol)时,您的程序才不会从终端看到任何内容,然后您的程序会获得整行,并且行结尾将被转换为Unix标准
\n
,无论终端实际上执行什么操作。此外,作为其中的一部分,终端驱动程序会回显键入的字符,以便用户可以看到他们正在输入什么。
在“熟悉”模式下,内核级终端驱动程序还会进行一些输出转换。其中的一部分是将
\n
转换为
\r\n
(如果需要)。
此外,在“熟悉”模式下,终端驱动程序处理特殊字符,如Control-C(向控制进程组发送SIGINT(由CPython转换为KeyboardInterrupt异常))和Control-Z(向控制进程组发送SIGTSTP(类似于SIGSTOP,但可以被捕获))。在“cbreak”模式下,不再进行行编辑。终端驱动程序立即将每个字符(或短字符序列,如箭头键的转义序列)发送给程序。这些字符不会回显到屏幕上,因此除非您的程序随后打印它们,否则用户将看不到它们。尽管终端驱动程序仍然处理Control-C和Control-Z等特殊字符,但它停止处理回退或单词删除字符(通常为Control-W)之类的行编辑字符。还有,仍然会进行一些输出处理,因此驱动程序将
\n
转换为
\r\n
。
在“raw”模式下,输入和输出都不进行任何处理。没有特殊字符处理,没有回显,没有将
\n
转换为
\r\n
,没有Control-Z处理,没有任何处理。它完全由将终端置于原始模式的程序执行所有操作。
现在,您正在设置
sys.stdin
的属性,因此您可能认为这不应影响
sys.stdout
。但实际上,您的两个文件描述符都引用了同一个“实例”的终端驱动程序。而且是终端驱动程序的设置决定发生什么。因此,无论您是否通过
sys.stdin
,
sys.stdout
甚至
sys.stderr
更改这些设置,所有更改都会影响相同的基础终端驱动程序实例和它们影响所有其他内容。
当然,在程序启动之前由shell重定向的文件描述符不适用于此规则。
另外提示,您可以在命令行上使用“stty -a”查看所有这些标志的完整读数(包括哪些控制字符在cooked和cbreak模式下导致哪些信号)。
fd
和sys.stdin
参数。termios.tcgetattr
需要一个具有fileno
属性的对象,而tty.setraw
则需要文件描述符(至少在 Python3.9 中是这样)。你使用的是哪个版本? - Adam Jenča