如何在Python中将输出多路复用到OS文件描述符?

时间:2011-03-06 19:51:39

标签: python subprocess

subprocess.Popen机制使用基础文件描述符而不是类文件对象来编写其stdout/stderr。我需要同时捕获stdoutstderr,同时仍然将它们显示在控制台上。

如何创建Popen可以使用的文件描述符,以便我这样做?

1 个答案:

答案 0 :(得分:1)

上下文:您指定的 stdin stdout stderr 对象的subprocess uses the raw file descriptors ,因为it passes them down to POSIX。如果您使用subprocess.PIPE,则会创建一个os.pipe()的新管道。此外,Popen.communicate读取直到流的末尾,如果您想将数据传输到其他位置,这可能是不可取的。

由于您要将输出打印到 stdout ,我假设它是文本输出。您需要在encoding errors中使用universal_newlinesPopensubprocess将文件视为文本(请参阅docs)。< / p>

import subprocess

p = subprocess.Popen(
    '/usr/bin/whoami',
    stdout=subprocess.PIPE,  # Control stdout
    universal_newlines=True  # Files opened in text mode
)

# Pipe the data somewhere else too, e.g.: a log file
with open('subprocess.log', 'w') as logfile:
    # p.poll() returns the return code when `p` exits
    while p.poll() is None:
        line = p.stdout.readline()
        # one to our stdout (readline includes the \n)
        print(line, end='')
        # one to the logfile
        logfile.write(line)

可以使用相同的技术来操作 stderr ,例如,将file=sys.stderr传递给print。请注意,您也可以直接传递来自您自己的 stdin

subprocess.Popen('/usr/bin/whoami', stdin=sys.stdin, stdout=subprocess.PIPE, ...)

毕竟,标准流只包装文件描述符。如果读到行的末尾不适合您期望的输出类型,则只需read一个非常短的缓冲区。

stderr stdout

同时工作

如果您同时需要 stdout stderr ,则会出现一个只能一次读取的问题。
一种可能性是使用os.set_blocking使管道不阻塞,以便在没有数据时立即返回任何read方法。这允许您在流之间交替 另一种可能性是,有两个独立的线程处理 stdout stderr ;但是有一种更简单的方法可以通过aysncio module

实现这一目标
import asyncio
import sys

PROCESS_PATH = '/bin/mixed_output'

class MultiplexProtocol(asyncio.SubprocessProtocol):
    def __init__(self, exit_future):
        self.exit_future = exit_future

    def pipe_data_received(self, fd, data):
        if fd == sys.stdout.fileno():
            print(data.decode('utf-8'), file=sys.stdout, end='')
        elif fd == sys.stderr.fileno():
            print(data.decode('utf-8'), file=sys.stderr, end='')

    def process_exited(self):
        self.exit_future.set_result(True)


async def launch_subprocess(loop):
    # Future marking the end of the process
    exit_future = asyncio.Future(loop=loop)
    # Use asyncio's subprocess
    create_subp = loop.subprocess_exec(
        lambda: MultiplexProtocol(exit_future),
        PROCESS_PATH,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
        stdin=None
    )
    transport, protocol = await create_subp
    await exit_future
    # Close the pipes
    transport.close()


loop = asyncio.get_event_loop()
loop.run_until_complete(launch_subprocess(loop))

这比在主机进程中不断循环以将数据传输到其他流所消耗的CPU少得多,因为MultiplexProtocol.pipe_data_received仅在需要时被调用。