簡體   English   中英

如何在Python中將輸出多路復用到OS文件描述符?

[英]How can I multiplex output to an OS file descriptor in Python?

subprocess.Popen機制使用底層文件描述符而不是類文件對象來編寫其stdout/stderr 我需要同時捕獲stdoutstderr同時仍然將它們顯示到控制台。

如何創建Popen可以使用的文件描述符,以便我這樣做?

只是一些上下文: subprocess 使用你指定的stdinstdoutstderr對象的原始文件描述符 ,因為它將它們傳遞給POSIX 如果你使用subprocess.PIPE ,那么它會創建一個新的管os.pipe() 此外, Popen.communicate讀取直到流的末尾,如果您想將數據傳輸到其他位置,這可能是不可取的。

由於您要將輸出打印到stdout ,我假設它是文本輸出。 您需要在Popen使用encodingerrorsuniversal_newlines進行subprocess Popen ,以將文件視為文本(請參閱文檔 )。

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一個非常短的緩沖區。

stderrstdout同時工作

如果你需要stdoutstderr ,你會遇到一個問題,你一次只能讀一個。
一種可能性是使用os.set_blocking使管道不阻塞,以便在沒有數據時立即返回任何read方法。 這允許您在流之間切換。
另一種可能性是,有兩個獨立的線程處理stdoutstderr ; 但是有一種更簡單的方法可以通過aysncio模塊實現這一aysncio

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM