首页 > 解决方案 > 通过 Python Subprocess' Popen over external 命令流式传输内存数据

问题描述

我想要达到的目标

这是一个有效的最小示例,它演示了我想要实现的目标:


    from io import StringIO
    from subprocess import Popen, PIPE
    import time

    proc_input = StringIO("aa\nbb\ncc\ndd")
    proc = Popen(["cat"], stdin=PIPE, stdout=PIPE)
    for line in  proc_input:
        proc.stdin.write(line.encode())
        yield proc.stdout.readline()
        time.sleep(1)

问题proc.stdout.readline()只是阻塞并且不显示任何内容。

我已经学到了什么


    import tempfile
    from subprocess import Popen, PIPE

    tp = tempfile.TemporaryFile()
    tp.write("aa\nbb\ncc\ndd".encode())
    tp.seek(0)
    proc = Popen(["cat"], stdin=tp, stdout=PIPE)
    for line in proc.stdout:
        print(line)


    proc_input = StringIO("aa\nbb\ncc\ndd")
    proc = Popen(["cat"], stdin=PIPE, stdout=PIPE)
    for line in  proc_input:
        proc.stdin.write(line.encode())
    proc.stdin.close()

    for line in proc.stdout:
            print(line)

我也尝试过

附加信息:我正在使用 Linux。

评论评论

建议将输入生成器分成块。这可以通过

   def PopenStreaming(process, popen_kwargs, nlines, input):
        while input:
            proc = Popen(process, stdin=PIPE, stdout=PIPE, **popen_kwargs)
            for n, row in enumerate(input):
                proc.stdin.write(row)
                if n == nlines:
                    proc.stdin.close()
                    break
            for row in proc.stdout:
                yield row

标签: pythoniostreamsubprocessgenerator

解决方案


我不确定是否总是可以做你想做的事情。https://docs.python.org/3/library/subprocess.html上的文档说

警告:使用communicate()而不是.stdin.write.stdout.read.stderr.read避免由于任何其他操作系统管道缓冲区填满并阻塞子进程而导致的死锁。

所以你应该使用communicate,但这意味着等待进程终止:

Popen.communicate(input=None, timeout=None)与进程交互:将数据发送到标准输入。从 stdout 和 stderr 读取数据,直到到达文件结尾。等待进程终止。

这意味着您只能使用communicate一次,这不是您想要的。

但是,我认为使用行缓冲文本模式应该是安全的,以避免死锁:

from subprocess import Popen, PIPE

kwargs = {
    "stdin": PIPE,
    "stdout": PIPE,
    "universal_newlines": True,  # text mode
    "bufsize": 1,  # line buffered
}

with Popen(["cat"], **kwargs) as process:
    for data in ["A\n", "B\n", "C\n"]:
        process.stdin.write(data)
        print("data sent:", data)
        output = process.stdout.readline()
        print("output received:", output)

如果这不适用于您的情况,也许您可​​以将呼叫拆分为多个较小的呼叫?使用check_output它的input关键字参数也可以简化你的代码:

from subprocess import check_output
output = check_output(["cat"], input=b"something\n")
print(output)

推荐阅读