使用 asyncio 将 bash 作为 Python 的子进程运行,但 bash 提示被延迟

Posted

技术标签:

【中文标题】使用 asyncio 将 bash 作为 Python 的子进程运行,但 bash 提示被延迟【英文标题】:Running bash as a subprocess to Python using asyncio, but bash prompts are delayed 【发布时间】:2020-06-08 11:00:31 【问题描述】:

我希望从 Python 的 asyncio 控制一个长时间运行的交互式 Bash 子进程,一次向它发送一个命令,然后从它接收结果。

下面的代码片段在 Python 3.7.0、Darwin Kernel 版本 16.7.0 中运行良好,除了 Bash 提示不会立即出现在 stderr 上,而是似乎“排队”直到有别的东西写信给stderr

这是一个问题,因为原程序需要接收 Bash 提示才能知道上一个命令已经完成。

from asyncio.subprocess import PIPE
import asyncio


async def run():
    proc = await asyncio.create_subprocess_exec(
        '/bin/bash', '-i', stdin=PIPE, stdout=PIPE, stderr=PIPE
    )

    async def read(stream):
        message = 'E' if stream is proc.stderr else 'O'
        while True:
            line = await stream.readline()
            if line:
                print(message, line)
            else:
                break

    async def write():
        for command in (b'echo PS1=$PS1', b'ls sub.py', b'ls DOESNT-EXIST'):
            proc.stdin.write(command + b'\n')
            await proc.stdin.drain()
            await asyncio.sleep(0.01)  # TODO: need instead to wait for prompt

    await asyncio.gather(
        read(proc.stderr),
        read(proc.stdout),
        write(),
    )


asyncio.run(run())

结果:

E b'bash: no job control in this shell\n'
O b'PS1=\\u@\\h:\\w$\n'
O b'sub.py\n'
E b'tom@bantam:/code/test/python$ tom@bantam:/code/test/python$ tom@bantam:/code/test/python$ ls: DOESNT-EXIST: No such file or directory\n'

注意最后三个提示都是一起出来的,只有一次是故意造成的错误。期望的行为当然是提示在出现时立即出现。

使用proc.stderr.read() 而不是proc.stderr.read() 会产生更多代码,但结果相同。

看到stderr 中出现bash: no job control in this shell 消息我有点惊讶,因为我正在运行bash -i 并且因为设置了$PS1,我想知道这是否与问题有关,但是无法更进一步。

【问题讨论】:

【参考方案1】:

这让我耽搁了半天,但在我写完问题后,我花了十分钟才想出一个解决方法。

如果我修改提示使其以\n 结尾,那么proc.stderr 实际上已被刷新,并且一切都运行得非常完美。

【讨论】:

以上是关于使用 asyncio 将 bash 作为 Python 的子进程运行,但 bash 提示被延迟的主要内容,如果未能解决你的问题,请参考以下文章

如何将协程添加到正在运行的 asyncio 循环中?

Python asyncio:处理潜在的无限列表

将 Python 脚本的返回值存储在 bash 脚本中

如何将 Celery 与 asyncio 结合使用?

如何将 asyncio 与 boost.python 一起使用?

将 cProfile 与 asyncio 代码一起使用的正确方法是啥?