使用子进程时如何在Python中复制tee行为?

sor*_*rin 64 python subprocess stdout stderr tee

我正在寻找一种Python解决方案,它允许我将命令的输出保存在文件中,而不会将其从控制台中隐藏.

仅供参考:我问的是tee(作为Unix命令行实用程序)而不是Python intertools模块中具有相同名称的函数.

细节

  • Python解决方案(不调用tee,在Windows下不可用)
  • 我不需要为被调用进程的stdin提供任何输入
  • 我无法控制被叫程序.我所知道的是它会向stdout和stderr输出一些内容并返回退出代码.
  • 在调用外部程序时工作(子进程)
  • 为两者stderr而努力stdout
  • 能够区分stdout和stderr,因为我可能只想显示一个控制台,或者我可以尝试使用不同的颜色输出stderr - 这意味着stderr = subprocess.STDOUT不起作用.
  • 实时输出(渐进式) - 该过程可以运行很长时间,我无法等待它完成.
  • Python 3兼容代码(重要)

参考

以下是我发现的一些不完整的解决方案:

图http://blog.i18n.ro/wp-content/uploads/2010/06/Drawing_tee_py.png

当前代码(第二次尝试)

#!/usr/bin/python
from __future__ import print_function

import sys, os, time, subprocess, io, threading
cmd = "python -E test_output.py"

from threading import Thread
class StreamThread ( Thread ):
    def __init__(self, buffer):
        Thread.__init__(self)
        self.buffer = buffer
    def run ( self ):
        while 1:
            line = self.buffer.readline()
            print(line,end="")
            sys.stdout.flush()
            if line == '':
                break

proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdoutThread = StreamThread(io.TextIOWrapper(proc.stdout))
stderrThread = StreamThread(io.TextIOWrapper(proc.stderr))
stdoutThread.start()
stderrThread.start()
proc.communicate()
stdoutThread.join()
stderrThread.join()

print("--done--")

#### test_output.py ####

#!/usr/bin/python
from __future__ import print_function
import sys, os, time

for i in range(0, 10):
    if i%2:
        print("stderr %s" % i, file=sys.stderr)
    else:
        print("stdout %s" % i, file=sys.stdout)
    time.sleep(0.1)
Run Code Online (Sandbox Code Playgroud) 实际输出
stderr 1
stdout 0
stderr 3
stdout 2
stderr 5
stdout 4
stderr 7
stdout 6
stderr 9
stdout 8
--done--
Run Code Online (Sandbox Code Playgroud)

预期产量是订购线.备注,不允许修改Popen只使用一个PIPE,因为在现实生活中我会想要用stderr和stdout做不同的事情.

即使在第二种情况下,我也无法获得像实时一样的实时,事实上所有的结果都是在过程结束时收到的.默认情况下,Popen应该不使用缓冲区(bufsize = 0).

小智 15

我看到这是一个相当古老的帖子,但万一有人还在寻找一种方法来做到这一点:

proc = subprocess.Popen(["ping", "localhost"], 
                        stdout=subprocess.PIPE, 
                        stderr=subprocess.PIPE)

with open("logfile.txt", "w") as log_file:
  while proc.poll() is None:
     line = proc.stderr.readline()
     if line:
        print "err: " + line.strip()
        log_file.write(line)
     line = proc.stdout.readline()
     if line:
        print "out: " + line.strip()
        log_file.write(line)
Run Code Online (Sandbox Code Playgroud)

  • -1:此解决方案导致任何可在stdout或stderr上生成足够输出的子进程的死锁,并且stdout/stderr不完全同步. (14认同)
  • 这对我有用,尽管我发现 `stdout, stderr = proc.communicate()` 更容易使用。 (2认同)
  • @kevinarpe错了.`readline(size)`不会修复死锁.应该同时读取stdout/stderr.请参阅使用线程或asyncio显示解决方案的问题下的链接. (2认同)

kal*_*ebo 8

如果需要 python 3.6 不是问题,现在有一种使用 asyncio 的方法。此方法允许您分别捕获 stdout 和 stderr,但仍然可以在不使用线程的情况下将两个流都传输到 tty。这是一个粗略的概述:

class RunOutput():
    def __init__(self, returncode, stdout, stderr):
        self.returncode = returncode
        self.stdout = stdout
        self.stderr = stderr

async def _read_stream(stream, callback):
    while True:
        line = await stream.readline()
        if line:
            callback(line)
        else:
            break

async def _stream_subprocess(cmd, stdin=None, quiet=False, echo=False) -> RunOutput:
    if isWindows():
        platform_settings = {'env': os.environ}
    else:
        platform_settings = {'executable': '/bin/bash'}

    if echo:
        print(cmd)

    p = await asyncio.create_subprocess_shell(cmd,
                                              stdin=stdin,
                                              stdout=asyncio.subprocess.PIPE,
                                              stderr=asyncio.subprocess.PIPE,
                                              **platform_settings)
    out = []
    err = []

    def tee(line, sink, pipe, label=""):
        line = line.decode('utf-8').rstrip()
        sink.append(line)
        if not quiet:
            print(label, line, file=pipe)

    await asyncio.wait([
        _read_stream(p.stdout, lambda l: tee(l, out, sys.stdout)),
        _read_stream(p.stderr, lambda l: tee(l, err, sys.stderr, label="ERR:")),
    ])

    return RunOutput(await p.wait(), out, err)


def run(cmd, stdin=None, quiet=False, echo=False) -> RunOutput:
    loop = asyncio.get_event_loop()
    result = loop.run_until_complete(
        _stream_subprocess(cmd, stdin=stdin, quiet=quiet, echo=echo)
    )

    return result

Run Code Online (Sandbox Code Playgroud)

上面的代码基于这篇博文:https : //kevinmccarthy.org/2016/07/25/streaming-subprocess-stdin-and-stdout-with-asyncio-in-python/


bad*_*adp 7

这是teePython 的直接移植。

import sys
sinks = sys.argv[1:]
sinks = [open(sink, "w") for sink in sinks]
sinks.append(sys.stderr)
while True:
  input = sys.stdin.read(1024)
  if input:
    for sink in sinks:
      sink.write(input)
  else:
    break
Run Code Online (Sandbox Code Playgroud)

我现在在Linux上运行,但这应该可以在大多数平台上使用。


现在的subprocess部分,我不知道你是怎么想“线”子进程的stdinstdoutstderr你的stdinstdoutstderr和文件接收器,但我知道你可以这样做:

import subprocess
callee = subprocess.Popen( ["python", "-i"],
                           stdin = subprocess.PIPE,
                           stdout = subprocess.PIPE,
                           stderr = subprocess.PIPE
                         )
Run Code Online (Sandbox Code Playgroud)

现在,您可以访问callee.stdincallee.stdoutcallee.stderr像正常的文件,使上面的“解决方案”的工作。如果您想获取callee.returncode,则需要额外致电callee.poll()

编写时要小心callee.stdin:如果在执行该操作时进程已退出,则可能会引发错误(在Linux上,我得到IOError: [Errno 32] Broken pipe)。

  • 这在Linux中不是最佳选择,因为Linux提供了一个临时的[`tee(f_in,f_out,len,flags)]](http://linux.die.net/man/2/tee)API,但这不是正确吗? (2认同)

小智 6

这是如何做到的

import sys
from subprocess import Popen, PIPE

with open('log.log', 'w') as log:
    proc = Popen(["ping", "google.com"], stdout=PIPE, encoding='utf-8')
    while proc.poll() is None:
        text = proc.stdout.readline() 
        log.write(text)
        sys.stdout.write(text)
Run Code Online (Sandbox Code Playgroud)

  • 对于任何想知道的人,是的,您可以使用 `print()` 而不是 `sys.stdout.write()`。:-) (2认同)