Python交互式shell在子进程中运行时不响应输入 [英] Python interactive shell not responding to input when run in subprocess

查看:151
本文介绍了Python交互式shell在子进程中运行时不响应输入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

from os import pipe, read, write从子流程导入 Popen从时间导入睡眠# 创建标准输入/标准输出管道out_read_pipe_fd, out_write_pipe_fd = pipe()in_read_pipe_fd, in_write_pipe_fd = pipe()# 启动进程proc = Popen(python", stdin=in_read_pipe_fd, stdout=out_write_pipe_fd,close_fds=真,壳=真)# 确保进程已启动睡觉(2)# 写东西到标准输入写(in_write_pipe_fd, b"print(\"hello world\")\n")# 一次读取所有写入 stdout 的数据 1 个字节打印(阅读:")为真:打印(repr(读取(out_read_pipe_fd,1)))

当我将 "python" 更改为 "myexe.exe" 时,上面的代码有效,其中 myexe.exe 是我的hello world用 C++ 编写的程序由 MinGW 编译.为什么会发生这种情况?这是完整的代码,但上面的例子显示我的问题.当我将 "python" 更改为 "cmd" 时,它也能正常工作.

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

Python 3.7.9 (tags/v3.7.9:13c94747c7, Aug 17 2020, 18:58:18) [MSC v.1900 64 位 (AMD64)] on win32输入帮助"、版权"、信用"或许可证"想要查询更多的信息.>>>

这意味着应该有内容写入stdout.

问题

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

<块引用>

解释器的操作有点像 Unix shell:当被调用时使用连接到 tty 设备的标准输入,它读取并执行交互式命令;当使用文件名参数或使用一个文件作为标准输入,它从中读取并执行一个脚本文件.

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

方案一:强制python交互运行

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

<块引用>

-i : 运行脚本后交互检查;甚至强制提示如果标准输入似乎不是终端;还有 PYTHONINSPECT=x

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

Popen(python -i", stdin=in_read_pipe_fd, stdout=out_write_pipe_fd,close_fds=真,壳=真)

一切都应该按预期开始工作.

方案二:伪装成终端

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

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

小笔记

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

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). The problem is that when I start a python process using subprocess, python doesn't write anything to stdout. I am not even sure if it reads what I wrote in 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. 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".

PS: when I run python from the command prompt it gives me:

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.

解决方案

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. It instead behaves as if you used the command cat script | python, which means it waits until stdin is closed, and then executes everything as a single script. 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; 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.

Solution 1: force python to run interactively

To solve your problem, we're gonna need python to ignore the fact it is not run interactively. When running python --help you might notice the flag -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("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

You might have heard of pty, a pseudo-terminal device. 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. You can use the python module pty to open one and connect it to the subprocess instead of a normal 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.

But that comes with a price - some programs, when connected to a tty, will also alter their output accordingly. For example, in many linux distributions, the grep command colors the matched pattern in the output. 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.

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

这篇关于Python交互式shell在子进程中运行时不响应输入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆