恰好将一个字符发送到子进程的标准输入

Send exactly one char to a subprocess' stdin

我正在尝试 unittest 一个从标准输入获取单个按键的模块。获取密钥的代码工作得很好,但是将一个字符(字节?)准确地写入子进程的标准输入给我带来了一些问题。

我基本上使用的是 推荐的内容,并根据文档和其他 SO 答案进行了修改:

for ch in range(0, 128):
    p = sp.Popen(
        [py, "-u", TEST_HELP, "getch"],
        stdin=sp.PIPE,
        stdout=sp.PIPE,
        stderr=sp.PIPE,
        bufsize=1
    )
    out, err = p.communicate(input=bytes(chr(ch), "ascii"))
    print(out, ",", err)

我想要的是 p 接收标准输入的一个 ASCII 字符然后退出。 ch 有时是 NULEOF 和其他控制字符这一事实不是问题;这正是我想要的。

问题是,在我按下 CTRL - C 之前,这似乎什么都不做,然后它会因键盘中断而退出。堆栈跟踪的最后一行是 in selectors.py: fd_event_list = self._poll.poll(timeout),它告诉我它正在等待超时(?),但我没有提供 timeout=int kwarg。

我正在使用的命令解析为 python3 -u helptest.py getch,它看起来像这样,当我自己从命令行 运行 它时它可以正常工作。

这是helptest的相关部分:

def getch():
    write_and_flush(ord(_ic._Getch()))

(write_and_flush 只是 运行s stdout.write; stdout.flush)

_ic._Getch()是:

def _Getch():
    if sys.stdin.isatty():
        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
    else:
        return sys.stdin.read(1)

我在 subprocess 调用中哪里做错了?


将调用更改为:

p = sp.Popen(
    [py, TEST_HELP, "getch"],
    stdin=sp.PIPE,
    stdout=sp.PIPE,
    stderr=sp.PIPE,
)
out, err = p.communicate(input=bytes(chr(ch) + "\n", "ascii"))

通过省略 bufsize、删除 --unbuffered 选项并添加 "EOL"(以及其中的变体)不会t/don改变任何内容。

您的代码表明您希望 bufsize=1 将缓冲区大小正好设置为 1,但它却打开了行缓冲模式。通信阻塞,直到写入 EOL。

最后一次打印调用应该有 Flush=True

如果要发送单个字节 (),

p.communicate() 代码是正确的。问题是你的 helptest.py.

Ctrl+C 行为表明 helptest.py 尝试直接从控制台读取而不是使用 stdin。似乎您使用 _Getch() from here 在 Windows 上使用 msvcrt.getch() 即,它可能从控制台读取而不是 stdin

此外,sys.stdin.read(1) 可能会读取一个以上的字节 -- 默认情况下 sys.stdin 处于文本模式。要从标准输入读取一个字节,您可以使用 b = os.read(0, 1) 或以二进制模式重新打开 sys.stdin,例如,在 Python 上调用 sys.stdin.detach() 3.

如果您的目的是阅读 key,那么您可以 use readchar package。最好在一个地方解决可能的(微妙的)问题。

无关:如果您使用 .communicate(),缓冲无关紧要,即,您可以删除 -ubufsize。除非子进程被破坏 input=s 应该表现得类似于 input=s + newline(即 EOF 是隐式 EOL)。