简体   繁体   English

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

[英]Python interactive shell not responding to input when run in subprocess

I am making a terminal command line interface program as part of a bigger project.我正在制作一个终端命令行界面程序作为更大项目的一部分。 I want the user to be able to run arbitrary commands (like in cmd).我希望用户能够运行任意命令(如在 cmd 中)。 The problem is that when I start a python process using subprocess , python doesn't write anything to stdout .问题是,当我使用subprocess启动python进程时, python 不会向stdout写入任何内容。 I am not even sure if it reads what I wrote in stdin .我什至不确定它是否读取了我在stdin中写的内容。 This is my code:这是我的代码:

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)))

The code above works when I change "python" to "myexe.exe" where myexe.exe is my hello world program written in C++ compiled by MinGW.当我将"python"更改为"myexe.exe"时,上面的代码有效,其中myexe.exe是我用 MinGW 编译的 C++ 编写的 hello world 程序。 Why does this happen?为什么会这样? This is the full code but the above example shows my problem. 是完整的代码,但上面的示例显示了我的问题。 It also works correctly when I change "python" to "cmd" .当我将"python"更改为"cmd"时,它也可以正常工作。

PS: when I run python from the command prompt it gives me: 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.
>>>

That means that there should be stuff written to stdout .这意味着应该有东西写入stdout

The Problem问题

Note that you are connecting python to a non-tty standard input, and so it behaves differently from when you just run the command python in your terminal.请注意,您将 python 连接到非 tty 标准输入,因此它的行为与您在终端中运行命令python时的行为不同。 It instead behaves as if you used the command cat script | python相反,它的行为就像您使用命令cat script | python cat script | python , which means it waits until stdin is closed, and then executes everything as a single script. cat script | python ,这意味着它会等到标准输入关闭,然后将所有内容作为单个脚本执行。 This behavior is described in the 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;解释器的操作有点像 Unix shell:当使用连接到 tty 设备的标准输入调用时,它以交互方式读取和执行命令; when called with a file name argument or with a file as standard input, it reads and executes a script from that file.当使用文件名参数或文件作为标准输入调用时,它会从该文件读取并执行脚本。

Try adding close(in_write_pipe_fd) before reading, and you'll see that it succeeds.尝试在读取之前添加close(in_write_pipe_fd) ,您会看到它成功了。

Solution 1: force python to run interactively方案一:强制 python 交互运行

To solve your problem, we're gonna need python to ignore the fact it is not run interactively.为了解决您的问题,我们需要 python 来忽略它不是交互式运行的事实。 When running python --help you might notice the flag -i :运行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

Sounds good:) Just change your Popen call to:听起来不错:) 只需将您的Popen调用更改为:

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

And stuff should start working as expected.东西应该开始按预期工作。

Solution 2: pretend to be a terminal解决方案2:伪装成终端

You might have heard of pty , a pseudo-terminal device.您可能听说过pty ,一种伪终端设备。 It is a feature in a few operating systems that allows you to connect a pipe to a tty driver instead of a terminal emulator, thus, in your case, allowing you write a terminal emulator yourself.这是一些操作系统中的一个功能,允许您将 pipe 连接到 tty 驱动程序而不是终端仿真器,因此,在您的情况下,允许您自己编写终端仿真器。 You can use the python module pty to open one and connect it to the subprocess instead of a normal pipe.您可以使用 python 模块pty打开一个并将其连接到子进程,而不是普通的 pipe。 That will trick python to think it is connected to an actual tty device, and will also allow you to emulate Ctrl-C presses, arrow-up/arrow-down, and more.这将欺骗 python 认为它已连接到实际的 tty 设备,并且还允许您模拟 Ctrl-C 按下、向上/向下箭头等。

But that comes with a price - some programs, when connected to a tty, will also alter their output accordingly.但这是有代价的——一些程序在连接到 tty 时,也会相应地改变它们的 output。 For example, in many linux distributions, the grep command colors the matched pattern in the output.例如,在许多 linux 发行版中, grep命令 colors 与 Z78E6221F3993D14CE5686F 中的匹配模式匹配。 If you don't make sure you can handle colors correctly in your program, or configure the tty to declare it doesn't support colors (and other tty features), you'll start getting garbage in some command's outputs.如果您不确定是否可以在程序中正确处理 colors,或者配置 tty 以声明它不支持 colors(和其他 tty 功能),您将开始在某些命令的输出中获取垃圾。

Small note小笔记

I do feel like this might not be the best method to achieve your goal.我确实觉得这可能不是实现目标的最佳方法。 If you describe it more in detail I might be able to help you think of an alternative:)如果您更详细地描述它,我可能会帮助您考虑替代方案:)

The python interpreter is more often used to run scripts from the command line than in interactive mode, therefore its interactive elements are not written to stdout else they would interfere with script output. python 解释器更常用于从命令行运行脚本而不是在交互模式下,因此其交互元素不会写入stdout ,否则它们会干扰脚本 output。 Nobody wants to have to remove the introductory text from the script output.没有人愿意从脚本 output 中删除介绍性文本。

To facilitate this, when interacting with the user, the interpreter uses the sys.displayhook method to deliberately send output to stdout otherwise nothing goes to stdout .为了促进这一点,在与用户交互时,解释器使用sys.displayhook方法故意将 output 发送到stdout ,否则不会发送到stdout The rest (eg the intro text, and >>> prompt) are written to stderr according to the docs :根据文档将 rest(例如介绍文本和>>>提示)写入stderr

  • stdin is used for all interactive input (including calls to input()); stdin 用于所有交互式输入(包括对 input() 的调用);
  • stdout is used for the output of print() and expression statements and for the prompts of input(); stdout 用于 print() 和表达式语句的 output 和 input() 的提示;
  • The interpreter's own prompts and its error messages go to stderr.解释器自己的提示及其错误消息 go 到 stderr。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM