Python:如何写入子进程的标准输入并实时读取其输出

Posted

技术标签:

【中文标题】Python:如何写入子进程的标准输入并实时读取其输出【英文标题】:Python: how to write to stdin of a subprocess and read its output in real time 【发布时间】:2021-02-16 18:58:45 【问题描述】:

我有 2 个程序。

第一个(实际上可以用任何语言编写,因此根本无法更改)如下所示:

#!/bin/env python3

import random

while True:
    s = input()  # get input from stdin
    i = random.randint(0, len(s))  # process the input
    print(f"New output i", flush=True)  # prints processed input to stdout

它永远运行,从stdin 读取一些内容,处理它并将结果写入stdout

我正在尝试使用asyncio 库在 Python 中编写第二个程序。 它将第一个程序作为子进程执行,并尝试通过其stdin 为其提供输入,并从其stdout 检索结果。

到目前为止,这是我的代码:

#!/bin/env python3

import asyncio
import asyncio.subprocess as asp


async def get_output(process, input):
    out, err = await process.communicate(input)
    print(err) # shows that the program crashes
    return out

    # other attempt to implement

    process.stdin.write(input)
    await process.stdin.drain()  # flush input buffer

    out = await process.stdout.read()  # program is stuck here
    return out


async def create_process(cmd):
    process = await asp.create_subprocess_exec(
        cmd, stdin=asp.PIPE, stdout=asp.PIPE, stderr=asp.PIPE)
    return process


async def run():
    process = await create_process("./test.py")

    out = await get_output(process, b"input #1")
    print(out) # b'New output 4'
    out = await get_output(process, b"input #2")
    print(out) # b''
    out = await get_output(process, b"input #3")
    print(out) # b''
    out = await get_output(process, b"input #4")
    print(out) # b''

async def main():
    await asyncio.gather(run())

asyncio.run(main())

我很难实现get_output 功能。它将一个字节串(根据.communicate() 方法的input 参数的需要)作为参数,将其写入程序的stdin,从其stdout 读取响应并返回它。

现在,只有第一次调用 get_output 才能正常工作。这是因为.communicate() 方法的实现调用了wait() 方法,从而有效地导致程序终止(它不是故意的)。这可以通过检查get_output 函数中err 的值来验证,这表明第一个程序到达EOF。因此,对get_output 的其他调用返回一个空字节串。

我尝试了另一种方法,甚至不太成功,因为程序卡在out = await process.stdout.read() 行。我还没弄清楚为什么。

我的问题是如何实现get_output 函数以(近)实时捕获程序的输出并保持运行?它不必使用asyncio,但我发现这个库是迄今为止最好的。

提前谢谢你!

【问题讨论】:

第一个程序是否保证只打印一行输出以响应它已读取的输入行?如果是这样,您可以将await process.stdout.read() 更改为await process.stdout.readline(),您的第二种方法应该可以工作。 (它现在卡住了,因为它试图在 EOF 之前读取所有内容,并且您的第一个程序永远不会退出,所以 EOF 永远不会发生。) 如果第一个程序不能保证只打印一行输出,那么这个如何实现就不清楚了,因为没有办法知道多少要读取的数据。 @user4815162342 .read(n).readline() 不起作用。实际上,没有.read*() 方法似乎有效。有没有办法queue 输出? 我想我现在在您的代码中发现了问题 - 请参阅发布的答案。 【参考方案1】:

如果第一个程序保证只打印一行输出以响应它读取的输入行,您可以将await process.stdout.read() 更改为await process.stdout.readline(),您的第二种方法应该可以工作。

它对您不起作用的原因是您的 run 函数有一个错误:它从不向子进程发送换行符。因此,子进程卡在input() 中并且永远不会响应。如果您在传递给get_output 的字节文字末尾添加\n,则代码可以正常工作。

【讨论】:

天哪,我怎么会错过这样的事情?谢谢!你知道我如何使用.read() 或等效的方法来读取尽可能多的字节,直到下一个提示? @Haltarys 没有办法做到这一点。 read() 一直读取到 EOF,对于 read(n)readexactly(n)read(n) 可以返回少于 n 个字节),您需要知道 n。您可以发明一个协议,其中子程序以某种方式宣布它将写入多少字节,或使用自定界编码等,但这些将需要修改它,您说这是禁止的。最佳方法将取决于精确的约束。

以上是关于Python:如何写入子进程的标准输入并实时读取其输出的主要内容,如果未能解决你的问题,请参考以下文章

Python子进程将数据定向到标准输入

Python asyncio子进程连续写入stdin和读取stdout/stderr

使用重定向的标准输入处理子进程中的 kbhit

在没有flush()和新行的子进程输出上进行非阻塞读取

Python 运行守护程序子进程并读取标准输出

在 Rust 中写入子进程的标准输入?