【问题标题】:Python in raw mode stdin print adds spaces原始模式下的 Python 标准输入打印添加空格
【发布时间】:2012-08-27 05:49:17
【问题描述】:

我需要在 Python 中将标准输入切换到非缓冲模式,这样我就可以从中读取单个字符。我设法让它工作,但现在标准输出被破坏了:不知何故,似乎在换行符之后,发出了一些空格字符,第一行为零,第二行为 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 中)。你用的是什么版本?

标签: python linux stdin buffering


【解决方案1】:

您遇到的问题是“raw”、“cooked”和“cbreak”模式之间的区别。这些模式是内核级终端驱动程序的模式,而不是您的应用程序代码或标准库或用户空间中的任何其他模式。这是引用这些的老式 Unix 方式。 Posix 已将它们替换为更细粒度的属性集,尽管 Posix 属性通常与辅助函数一起翻转,以模仿旧的“raw”、“cooked”和“cbreak”模式。

在熟模式下,终端驱动程序本身具有内置的原始行编辑功能。它处理退格、单词擦除(基本上一次退格整个单词)和类似的事情。没有什么比处理箭头键或历史记录或类似的东西更复杂了。很原始。在这种模式下,您的程序在发送行尾 (eol) 字符之前永远不会看到来自终端的任何内容,然后您的程序会得到一整行,并且行尾将被转换为 Unix 标准 \n 无论如何终端确实如此。此外,作为其中的一部分,终端驱动程序会将输入的字符回显到终端,以便用户可以看到他们正在输入的内容。

在“cooked”模式下,内核级终端驱动程序也会进行一些输出转换。其中一部分是将\n 转换为\r\n(如果需要)。

此外,在“cooked”模式下,终端驱动程序处理特殊字符,例如 Control-C(向控制进程组发送 SIGINT(由 CPython 转换为 KeyboardInterrupt 异常))和 Control-Z(发送 SIGTSTP(如SIGSTOP,但可以被捕获)到控制进程组)。

在“cbreak”模式下,不再进行行编辑。终端驱动程序立即将每个字符(或短字符序列,如箭头键的转义序列)提供给程序。这些字符不会回显到屏幕上,因此除非您的程序随后打印它们,否则用户将看不到它们。虽然终端驱动程序仍然处理特殊字符,如 Control-C 和 Control-Z,但它不再处理行编辑字符,如退格或字擦除字符(通常是 Control-W)。此外,一些输出处理仍然完成,因此驱动程序将\n 转换为\r\n

在“原始”模式下,不对输入或输出进行任何处理。没有特殊字符处理,没有回显,没有将\n 转换为\r\n,没有对 Control-Z 进行处理,什么都没有。这一切都取决于将终端置于原始模式的程序。

现在,您正在为sys.stdin 设置属性,因此您可能认为这不应该影响sys.stdout。但是,事实上,您的两个文件描述符都会导致终端驱动程序的完全相同的“实例”。决定发生什么的是终端驱动程序的设置。因此,如果您通过sys.stdinsys.stdout 甚至sys.stderr 更改这些设置并不重要,它们都会更改相同的底层终端驱动程序实例,并且它们会影响所有其他设置。

当然,对于在程序启动之前已被 shell 重定向的文件描述符,情况并非如此。

附带说明一下,您可以在命令行上使用stty -a 来查看所有这些标志的完整读数(包括在cbreak 和cbreak 模式下哪些控制字符导致哪些信号)。

【讨论】:

  • 这是一个比我的 +1 更好更详细的答案
  • @TheDavidFactor - 我错过的一件事是,显然 posix 已经在很大程度上取消了驱动程序级别的谨慎模式,而是将这些模式变成了一个更细粒度的很大程度上独立标志的集合。
【解决方案2】:

当我在寻找同样问题的答案时,Google 把我带到了这里。没有回车的halex分享的线索帮助我寻找真相。我在 Chris 的 Wiki 上的帖子中找到了答案:https://utcc.utoronto.ca/~cks/space/blog/unix/CBreakAndRaw,这让我在这里阅读了 tty.py 的来源:https://hg.python.org/cpython/file/618ea5612e83/Lib/tty.py 这让我得出结论,如果目标是阅读单个字符,而不是:

tty.setraw()

用途:

tty.setcbreak()

【讨论】:

    【解决方案3】:

    看起来你只是在做一个换行而不是回车。将您的打印更改为

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

    【讨论】:

    • 到目前为止这似乎是正确的;我的印象是我永远不应该在 Linux 下手动插入\r。谢谢
    • @halex 你能解释一下为什么在原始模式下需要这样做,但在正常打印时不需要?谢谢。
    • 原始模式下某些命令的解释不同。这是从过去的终端和虚拟终端(以及最终的电传打字机)继承的僵化惯例。保留它是为了作为事实上的标准向后兼容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-02-09
    • 2021-11-28
    • 1970-01-01
    • 2013-05-01
    • 1970-01-01
    • 2013-12-05
    • 1970-01-01
    相关资源
    最近更新 更多