记录了subprocess.communicate()?

时间:2013-10-17 09:32:45

标签: python logging subprocess

我必须从Python调用脚本并收集其输出。所以,

p = subprocess.Popen ("script", shell = False, stdin = subprocess.PIPE, stdout = subprocess.PIPE)
out_lines = p.communicate ("\n".join (in_lines)) [0]

...除了我想记录每个out_line因为它出现的情况,你知道,最坏的情况发生(在子进程或主进程中)。

我有

  1. 无法控制script
  2. 不想复制&在我的Python
  3. 中修补communicate()的源代码
  4. 无法保证脚本会为每个输入行返回输出行。
  5. 最好避免调用依赖于平台的tee实用程序。
  6. 除了这四种可行但不方便的解决方案外,还有什么我可以忽略的吗?有可能用日志包装器替换stdout = PIPE吗?

    谢谢。我整个星期都会在这里。

3 个答案:

答案 0 :(得分:1)

subprocess.communicate的行动依赖于平台检测。在Windows上,工作是使用线程完成的,只需使用文件包装器就可以进行日志记录。

但是,在Unix上,subprocess使用select,它依赖于获取文件描述符(file.fileno()),因此这种技术不起作用。有可能只是创建另一个管道并在python中复制输出,但是它涉及更多,并且因为你无论如何都在编写平台相关代码,所以在Unix上你通常可以使用tee命令来完成目的

了解这一点,这是一个满足您要求的平台相关示例:

import subprocess
import sys

class FileWrapperWithLog(object):
    def __init__(self, file_object, filename):
        self.f= file_object
        self.log= open(filename, 'wb')
    def read(self):
        data= self.f.read()
        self.log.write(data)
        return data
    def close(self):
        return self.f.close()

FILENAME="my_file.log"
if sys.platform == "win32":
    p= subprocess.Popen('dir', shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    p.stdout= FileWrapperWithLog( p.stdout, FILENAME )
else:
    p= subprocess.Popen('ls | tee '+FILENAME, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
p.communicate()

另一种选择是猴子修补subprocess,但这将是一个容易出错的过程,因为沟通是一种复杂的方法,并且具有前面提到的与平台相关的行为。

答案 1 :(得分:1)

你基本上有两个重叠的控制线程。

  1. 将输入发送到子流程。
  2. 在子流程可用时读取数据。
  3. 以独立于平台的方式执行此操作并不会为您提供太多选项,除了使用线程(或者可能是选择循环)。

    您的代码似乎只对stdout感兴趣,因此您可以调用一个读取stdout并将内容写入文件的线程。

    以下是一个例子:

    import subprocess
    import os
    import threading
    
    
    class LogThread(threading.Thread):
        """Thread which will read from `pipefd` and write all contents to
        `fileobj` until `pipefd` is closed.  Used as a context manager, this thread
        will be automatically started, and joined on exit, usually when the
        child process exits.
        """
        def __init__(self, pipefd, fileobj):
            self.pipefd = pipefd
            self.fileobj = fileobj
            super(LogThread, self).__init__()
            self.setDaemon(1)
            self.start()
    
        def run(self):
            while True:
                line = self.pipefd.readline()
                if not line:
                    break
                self.fileobj.write(line)
                self.fileobj.flush()
    
        def __enter__(self):
            return self
    
        def __exit__(self, exc_type, exc_value, traceback):
            self.join()
    
    
    # Here's how to use the LogThread.    
    p = subprocess.Popen ("script", shell = False, stdin = subprocess.PIPE, stdout = subprocess.PIPE)
    with open('logfile.txt', 'wt') as logfile:
        with LogThread(p.stdout, logfile):
            p.stdin.write("\n".join(in_lines))
            p.stdin.close()
    

    这可能会复制Popen.communicate()的一小部分,但它不是很多代码,而且与平台无关。

    关于缓冲的说明: 将stdout缓冲到非tty设备(例如管道)是正常的。通常,stderr不会被缓冲。您通常无法控制正在运行的应用程序是否缓冲其输出。最好你可以猜测它是如何决定是否使用缓冲,大多数应用程序调用{​​{1}}来确定它是否应该缓冲。因此,在日志文件上设置缓冲0可能不是避免缓冲的正确解决方案。如果缓冲为0,则输出的每个字符都被写为单个isatty()调用,效率非常低。上述解决方案已经过修改,可以执行行缓冲。

    以下链接可能有用:https://unix.stackexchange.com/questions/25372/turn-off-buffering-in-pipe

答案 2 :(得分:1)

以下简单脚本说明了可以使用的方法(跨平台):

from subprocess import Popen, PIPE
import sys
import threading

def handle_line(line):
    print(line) # or log it, or whatever

def reader(stream):
    while True:
        s = stream.readline()
        if not s:
            break
        handle_line(s)
    stream.close()

p = Popen(sys.argv[1].split(), stdout=PIPE, stderr=PIPE, stdin=PIPE)
# Get threads  ready to read the subprocess output
out_reader = threading.Thread(target=reader, args=(p.stdout,))
err_reader = threading.Thread(target=reader, args=(p.stderr,))
out_reader.start()
err_reader.start()
# Provide the subprocess input
p.stdin.write("Hello, world!")
p.stdin.close()
# Wait for the child process to complete
p.wait()
# And for all its output to be consumed
out_reader.join()
err_reader.join()
print('Done.')

使用与其stdin相呼应的程序运行时,例如cat(或在Windows上,Gnu-Win32 cat.exe),您应该得到:

Hello, world!
Done.

作为输出。这应该适用于更大的输出 - 我在python-gnupg中使用这种技术,我需要在它们进入时处理行(来自stderr),而不是最后的所有行(这就是为什么我无法&# 39; t使用communicate)。

更新:" OOP的细节有很多种方式。可以有条理 - 我不会特别找到Austin Phillips'对我有用的版本。但是,我已经根据个人的需要,以最简单的方式展示了需要采取的步骤,并且可以建立在最基本的步骤之上。