Python在原始模式下,stdin的print会添加空格

12

我需要在Python中将标准输入切换到非缓冲模式,以便可以逐个字符地读取它。我已经成功实现了这一点,但是现在标准输出出现了问题:似乎在换行符后,会发出一些空格字符,第一行是0,第二行是3,第三行是6等等,如下所示:

ASD
   ASD
      ASD

操作系统是Ubuntu Linux 12.04,64位版本,Python版本为3.2.3。

我该如何摆脱这种行为?

以下是我使用的代码:

import sys
import tty
import termios

fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
tty.setraw(sys.stdin)

for i in range(0, 10):
    print("ASD")

termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

看起来你交换了 fdsys.stdin 参数。termios.tcgetattr 需要一个具有 fileno 属性的对象,而 tty.setraw 则需要文件描述符(至少在 Python3.9 中是这样)。你使用的是哪个版本? - Adam Jenča
3个回答

19
您遇到的问题是“原始”、“熟悉”和“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模式下导致哪些信号)。

3
这个回答比我的更好,更详细。+1 - TheDavidFactor
@TheDavidFactor - 我错过的一件事是,显然posix在驱动程序级别上基本上已经取消了离散模式,而是将这些模式转化为一个更细粒度的、大多数是独立标志集合。 - Omnifarious

10

6

看起来你只是在进行换行,而没有进行回车。请将打印内容更改为

print("ASD", end="\r\n")

到目前为止,这似乎是正确的。我原本以为在Linux下永远不应该手动插入\r。谢谢。 - K.Steff
1
@halex,你能解释一下为什么在原始模式下需要这个,但正常打印时不需要吗?谢谢。 - Stefan
1
一些命令在原始模式下的解释与常规模式不同。这是从昔日终端和虚拟终端(最终是电传打字机)继承下来的陈旧惯例。为了向后兼容而保留了这种做法,成为事实上的标准。 - MRule
1
答案完全正确,它可以工作,但同时也没有用处。@MRule准确指出了问题所在。谢谢。如何同时保持打印功能和原始模式?..... - jaromrax

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接