使用子流程时如何在python中复制tee行为?


71

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

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

细节

  • Python解决方案(不调用tee,在Windows下不可用)
  • 我不需要为调用的进程提供任何输入到stdin
  • 我无法控制被调用的程序。我所知道的是,它将向stdout和stderr输出一些内容,并返回退出代码。
  • 在调用外部程序时工作(子过程)
  • 要对工作都stderrstdout
  • 之所以能够区分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)
实际输出
stderr 1
stdout 0
stderr 3
stdout 2
stderr 5
stdout 4
stderr 7
stdout 6
stderr 9
stdout 8
--done--

预期的输出是将行排序。备注,不允许将Popen修改为仅使用一个PIPE,因为在现实生活中,我将要使用stderr和stdout做不同的事情。

同样,即使在第二种情况下,我也无法获得实时的实时显示,实际上,所有结果都是在过程完成时收到的。默认情况下,Popen应该不使用缓冲区(bufsize = 0)。




1
Python Popen的可能重复项:同时写入标准输出和日志文件,因为这是社区Wiki,所以以这种方式投票:-)
Ciro Santilli郝海东冠状病六四事件法轮功

Answers:


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)

这对我stdout, stderr = proc.communicate()有用,尽管我发现它更易于使用。
Chase Seibert,2012年

20
-1:此解决方案会导致任何子进程陷入僵局,这些子进程可能会在stdout或stderr上生成足够的输出,并且stdout / stderr不能完全同步。
jfs

@JFSebastian:是的,但是您可以通过替换readline()为解决该问题readline(size)。我用其他语言做了类似的事情。参考:docs.python.org/3/library/io.html#io.TextIOBase.readline
kevinarpe 2015年

5
@kevinarpe错误。readline(size)不会解决僵局。应该同时读取stdout / stderr。请参阅问题下方的链接,这些链接显示使用线程或异步的解决方案。
jfs 2015年

@JFSebastian如果我只想读取其中一个流,是否存在此问题?
ThorSummoner

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

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


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

import subprocess
callee = subprocess.Popen( ["python", "-i"],
                           stdin = subprocess.PIPE,
                           stdout = subprocess.PIPE,
                           stderr = subprocess.PIPE
                         )

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

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


2
在Linux中,这是次优的,因为Linux提供了临时tee(f_in, f_out, len, flags)API,但这不是重点吗?
badp 2010年

1
我更新了问题,问题是我无法找到如何使用子流程来逐渐从两个管道中获取数据,而在流程结束时无法一次全部获取。
索林2010年

我知道您的代码应该可以工作,但是有一个很小的要求确实破坏了整个逻辑:我希望能够区分stdout和stderr,这意味着我必须阅读它们两者,但我不知道哪个会获取新数据。请看一下示例代码。
索林2010年

1
@Sorin,这意味着您将不得不使用两个线程。一读stdout,一读stderr。如果要将两者都写入同一文件,则在开始读取时可以在接收器上获得一个锁,并在写入行终止符后将其释放。:/
badp 2010年

为此使用线程听起来并不吸引我,也许我们还会发现其他东西。奇怪的是,这是一个常见问题,但是没有人提供完整的解决方案。
索林2010年

5

这是可以做到的

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)

2
对于任何想知道的人,可以使用print()代替sys.stdout.write()。:-)
progyammer '19

@progyammerprint将添加一个额外的换行符,当您需要如实地重现输出时,这不是您想要的。
ivan_pozdeev

是的,但print(line, end='')可以解决问题
Danylo Zhydyk '20

5

如果不需要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

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


1

如果您不想与流程进行交互,则可以使用子流程模块。

例:

测试器

import os
import sys

for file in os.listdir('.'):
    print file

sys.stderr.write("Oh noes, a shrubbery!")
sys.stderr.flush()
sys.stderr.close()

testing.py

import subprocess

p = subprocess.Popen(['python', 'tester.py'], stdout=subprocess.PIPE,
                     stdin=subprocess.PIPE, stderr=subprocess.PIPE)

stdout, stderr = p.communicate()
print stdout, stderr

根据您的情况,您可以简单地先将stdout / stderr写入文件。您也可以通过通讯将参数发送到您的流程,尽管我无法弄清楚如何与子流程进行持续交互。


2
这不会在STDOUT的上下文中向您显示STDERR中的错误消息,这几乎使调试shell脚本等变得不可能。
RobM 2010年

含义...?在此脚本中,通过STDERR传递的所有内容都将与STDOUT一起打印到屏幕上。如果您指的是返回码,只需使用p.poll()即可检索它们。
韦恩·维尔纳

1
这不满足“渐进式”条件。
ivan_pozdeev

-1

尝试这个 :

import sys

class tee-function :

    def __init__(self, _var1, _var2) :

        self.var1 = _var1
        self.var2 = _var2

    def __del__(self) :

        if self.var1 != sys.stdout and self.var1 != sys.stderr :
            self.var1.close()
        if self.var2 != sys.stdout and self.var2 != sys.stderr :
            self.var2.close()

    def write(self, text) :

        self.var1.write(text)
        self.var2.write(text)

    def flush(self) :

        self.var1.flush()
        self.var2.flush()

stderrsav = sys.stderr

out = open(log, "w")

sys.stderr = tee-function(stderrsav, out)

这正是我要建议的方法。还值得添加一些文件数据描述符,例如closed
RobM 2010年

3
刚刚尝试过,subprocess.Popen调用fileno(),触发了异常。
RobM 2010年

-1

我的解决方案并不优雅,但可以。

您可以使用Powershell来访问WinOS下的“ tee”。

import subprocess
import sys

cmd = ['powershell', 'ping', 'google.com', '|', 'tee', '-a', 'log.txt']

if 'darwin' in sys.platform:
    cmd.remove('powershell')

p = subprocess.Popen(cmd)
p.wait()

ping在MacOS中给出无效的命令行错误消息。
ivan_pozdeev
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.