Python 交互式 shell 在子进程中 运行 时不响应输入

Python interactive shell not responding to input when run in subprocess

我正在制作一个终端命令行界面程序作为一个更大项目的一部分。我希望用户能够 运行 任意命令(比如在 cmd 中)。问题是,当我使用 subprocess 启动 python 进程时,python 不会向 stdout 写入任何内容。我什至不确定它是否读取了我在 stdin 中写的内容。这是我的代码:

from os import pipe, read, write
from subprocess import Popen
from time import sleep

# Create the stdin/stdout pipes
out_read_pipe_fd, out_write_pipe_fd = pipe()
in_read_pipe_fd, in_write_pipe_fd = pipe()

# Start the process
proc = Popen("python", stdin=in_read_pipe_fd, stdout=out_write_pipe_fd,
             close_fds=True, shell=True)

# Make sure the process started
sleep(2)

# Write stuff to stdin
write(in_write_pipe_fd, b"print(\"hello world\")\n")

# Read all of the data written to stdout 1 byte at a time
print("Reading:")
while True:
    print(repr(read(out_read_pipe_fd, 1)))

当我将 "python" 更改为 "myexe.exe" 时,上面的代码有效,其中 myexe.exe 是我用 MinGW 编译的 C++ 编写的 hello world 程序。为什么会这样? This 是完整代码,但上面的示例显示了我的问题。当我将 "python" 更改为 "cmd".

时它也能正常工作

PS:当我从命令提示符 运行 python 时,它给我:

Python 3.7.9 (tags/v3.7.9:13c94747c7, Aug 17 2020, 18:58:18) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>

这意味着应该有东西写入stdout

python 解释器更常用于命令行中的 运行 脚本而不是交互模式,因此它的交互元素不会写入 stdout 否则它们会干扰脚本输出。没有人愿意从脚本输出中删除介绍性文本。

为方便起见,在与用户交互时,解释器使用 sys.displayhook method to deliberately send output to stdout otherwise nothing goes to stdout. The rest (e.g. the intro text, and >>> prompt) are written to stderr according to the docs:

  • stdin is used for all interactive input (including calls to input());
  • stdout is used for the output of print() and expression statements and for the prompts of input();
  • The interpreter’s own prompts and its error messages go to stderr.

问题

请注意,您正在将 python 连接到非 tty 标准输入,因此它的行为与您在终端中 运行 命令 python 时的行为不同。相反,它的行为就像您使用了命令 cat script | python,这意味着它会等到 stdin 关闭,然后将所有内容作为单个脚本执行。 docs:

中描述了此行为

The interpreter operates somewhat like the Unix shell: when called with standard input connected to a tty device, it reads and executes commands interactively; when called with a file name argument or with a file as standard input, it reads and executes a script from that file.

在阅读之前尝试添加close(in_write_pipe_fd),您会看到它成功了。

解决方案 1:强制 python 交互地 运行

为了解决您的问题,我们需要 python 忽略它不是 运行 交互的事实。当 运行ning python --help 你可能会注意到标志 -i:

-i     : inspect interactively after running script; forces a prompt even
         if stdin does not appear to be a terminal; also PYTHONINSPECT=x

听起来不错 :) 只需将您的 Popen 调用更改为:

Popen("python -i", stdin=in_read_pipe_fd, stdout=out_write_pipe_fd,
      close_fds=True, shell=True)

东西应该会按预期开始工作。

解决方案 2:伪装成终端

您可能听说过 pty,一种伪终端设备。它是一些操作系统中的一项功能,允许您将管道连接到 tty 驱动程序而不是终端仿真器,因此,在您的情况下,允许您自己编写终端仿真器。您可以使用 python 模块 pty 打开一个并将其连接到子进程而不是普通管道。这将使 python 认为它已连接到实际的 tty 设备,并且还可以让您模拟 Ctrl-C 按下、arrow-up/arrow-down 等。

但这是有代价的——一些程序在连接到 tty 时,也会相应地改变它们的输出。例如,在许多 linux 发行版中,grep 命令为输出中的匹配模式着色。如果您不确定您可以在程序中正确处理颜色,或者配置 tty 以声明它不支持颜色(和其他 tty 功能),您将开始在某些命令的输出中出现垃圾。

小记

我确实觉得这可能不是实现您目标的最佳方法。如果您更详细地描述它,我也许可以帮助您想到一个替代方案:)