首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >登录subprocess.communicate()?

登录subprocess.communicate()?
EN

Stack Overflow用户
提问于 2013-10-17 09:32:44
回答 3查看 652关注 0票数 1

我必须从Python调用一个脚本并收集它的输出。所以,

代码语言:javascript
复制
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中复制和修补communicate()的源代码
  3. 不能保证脚本为每个输入行返回一个输出行。
  4. 最好避免调用平台相关的tee实用程序。

除了这四种可行但不方便的解决方案之外,还有什么是我忽略的吗?比如用日志包装器代替stdout = PIPE

谢谢。我会在这里待一周。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2013-10-17 12:15:26

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

  1. 向子进程发送输入。
  2. 读取子进程中的数据。

除了使用线程(或者可能是select循环)之外,独立于平台的方法不会给您提供太多的选项。

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

下面是一个例子:

代码语言:javascript
复制
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()的小部分,但它并不是很多代码,而且与平台无关。

关于缓冲的注意事项:标准输出被缓冲到非tty设备(如管道)是正常的。通常,stderr不会被缓冲。您通常无法控制正在运行的应用程序是否缓冲其输出。充其量,您可以猜测它如何决定是否使用缓冲,大多数应用程序都调用isatty()来确定是否应该进行缓冲。因此,在日志文件上设置缓冲0可能不是避免缓冲的正确解决方案。如果缓冲为0,则输出的每个字符都被编写为一个write()调用,并且效率很低。对上述解决方案进行了修改,以执行行缓冲。

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

票数 2
EN

Stack Overflow用户

发布于 2013-10-17 10:58:25

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

但是,在Unix上,subprocess使用select,它依赖于获取文件描述符(file.fileno()),因此这种技术不起作用。只需创建另一个管道并在python中复制输出就可以了,但它涉及的问题要多一点,而且由于您正在编写依赖于平台的代码,所以在Unix上通常可以使用tee命令来实现这个目的。

知道了这一点,下面是一个平台相关的满足您的需求的示例:

代码语言:javascript
复制
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
EN

Stack Overflow用户

发布于 2013-10-17 22:47:20

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

代码语言:javascript
复制
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 (或者,在cat上,Gnu 32 cat.exe),您应该得到:

代码语言:javascript
复制
Hello, world!
Done.

作为输出。这应该适用于更大的输出--我在python-gnupg中使用了这种技术,在这里,我需要处理行(来自stderr),而不是在最后处理所有行(这就是我不能使用communicate的原因)。

更新:有很多方法可以构造"OOP细节“--我不认为奥斯丁菲利普斯的版本对我那么有用。然而,我已经展示了需要以最简单的方式采取的步骤,这些步骤可以根据个人的需要建立在此基础上。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/19423008

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档