子进程输出(stdout/stderr)的包装

10

我希望能够通过Python的subprocess来捕获并显示进程的输出。

我认为我可以将我的文件对象作为命名参数stdout和stderr传递。

我可以看到它访问了fileno属性 - 所以它正在处理该对象。 然而,write()方法从未被调用。我的方法完全错误还是我只是忽略了什么?

class Process(object):
    class StreamWrapper(object):
        def __init__(self, stream):
            self._stream = stream
            self._buffer = []
        def _print(self, msg):
            print repr(self), msg
        def __getattr__(self, name):
            if not name in ['fileno']:
                self._print("# Redirecting: %s" % name)
            return getattr(self._stream, name)
        def write(self, data):
            print "###########"
            self._buffer.append(data)
            self._stream.write(data)
            self._stream.flush()
        def getBuffer(self):
            return self._buffer[:]
    def __init__(self, *args, **kwargs):
        print ">> Running `%s`" % " ".join(args[0])
        self._stdout = self.StreamWrapper(sys.stdout)
        self._stderr = self.StreamWrapper(sys.stderr)
        kwargs.setdefault('stdout', self._stdout)
        kwargs.setdefault('stderr', self._stderr)
        self._process = subprocess.Popen(*args, **kwargs)
        self._process.communicate()

更新:

我希望能够使用ANSI控制字符来移动光标并覆盖之前输出的内容,这也是我想要解决的问题。我不知道这是否是正确的术语,但以下是我所指的示例:我正在尝试自动化一些GIT操作,他们有一个进度条,在每次更新时都不需要写入新的一行。

更新2

对我来说,很重要的一点是子进程的输出可以立即显示出来。我已经尝试使用subprocess.PIPE来捕获输出,并手动显示它,但我只能在进程完成后才看到输出。然而,我希望能够实时地看到输出。


我更喜欢一个跨平台兼容的解决方案。 - phant0m
这里有一个非常好的解释,介绍如何使用非常有用和整洁的subprocess模块 - Yonatan Simson
4个回答

12

进程的标准输入、标准输出和标准错误需要是真正的文件描述符。(这实际上不是Python强加的限制,而是管道在操作系统级别上的工作方式。)因此,您需要一种不同的解决方案。

如果您想实时跟踪stdoutstderr,则需要异步I/O或线程。

  • 异步I/O: 使用标准的同步(=阻塞)I/O,对其中一个流的读取可能会被阻止,从而不允许实时访问另一个流。如果您在Unix上,则可以使用非阻塞I/O,如此答案中所述。但是,在Windows上,您将无法使用此方法。有关Python中异步I/O的更多信息以及一些替代方案,请参见此视频

  • 线程: 处理此问题的另一种常见方法是为要实时从中读取的每个文件描述符创建一个线程。线程仅处理它们分配的文件描述符,因此阻塞I/O不会产生影响。


非常感谢提供链接,我会查看这个。也许一旦我观看了视频,这个问题就会得到澄清,但是问一下也无妨:(还不确定我是否已经正确理解您的意思)如果它阻塞了很短的时间也没关系。回到GIT的例子,克隆可能需要几分钟,每隔几秒钟更新信息就足够了,不必是“字面实时”-不确定这对这个概念是否有影响 :) - phant0m
1
@phant0m:阻塞I/O的问题在于你无法知道调用会阻塞多长时间。如果你调用subproc.stderr.readline(),它将一直阻塞,直到进程向其stderr写入一行内容。如果进程从未这样做,你将永远无法捕获进程的stdout,直到进程结束。如果你只想捕获stdout,那没问题。一旦有两个文件描述符可读,阻塞I/O就不再有帮助了。 - Sven Marnach
啊,我明白了。那么我也可以从子进程中readline()并且让任何stderr输出引发异常?然后我可以之后读取所有已经打印到stderr的内容,对吗? - phant0m
@phant0m:我不理解你的最后一个问题。如果按照链接答案所述将stdout设置为非阻塞模式,然后调用subproc.stdout.readline(),有两种情况:1.有一行数据可用:它将从进程中读取并返回。2.没有完整的行可用:由于readline()不再允许阻塞,它将抛出一个IOError,你可以捕获它。 - Sven Marnach

0

类文件对象不够。必须是具有实际文件描述符的实际文件。使用subprocess对管道的支持,并根据需要从中读取。


事实是,我不知道如何同时做到这两件事。 - phant0m

0

请看这里

p = subprocess.Popen(cmd,
                 shell=True,
                 bufsize=64,
                 stdin=subprocess.PIPE,
                 stderr=subprocess.PIPE,
                 stdout=subprocess.PIPE)

4
我希望实时显示输出结果,但当我在测试中使用管道(PIPE)时,无法做到实时显示,只能在进程结束后一次性输出所有内容。 - phant0m

-2

你在类里面嵌套另一个类有什么原因吗?而且stdout和stderr可以像line一样接受任何文件。所以只需传递一个打开的文件类型或stringIO即可修改流。

import sys
sys.stdout = open('test.txt','w')
print "Testing!"
sys.stdout.write('\nhehehe')
sys.stdout = sys.__stdout__
sys.exit(0)

这个类在里面,因为我认为将其放在外部类的命名空间中更好看,以便清楚它的用途。无论如何,我不打算输出到文件。我想要打印到标准输出并捕获数据以进行进一步处理。 - phant0m

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接