239

我正在使用 python 脚本作为流体动力学代码的驱动程序。当需要运行模拟时,我会运行代码,将subprocess.Popen输出收集stdout到--- 然后我可以打印(并保存到日志文件)输出信息,并检查是否有任何错误。问题是,我不知道代码是如何进行的。如果我直接从命令行运行它,它会输出关于它的迭代时间、时间、下一个时间步长等信息。stderrsubprocess.PIPE

有没有办法既存储输出(用于日志记录和错误检查),又产生实时流输出?

我的代码的相关部分:

ret_val = subprocess.Popen( run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True )
output, errors = ret_val.communicate()
log_file.write(output)
print output
if( ret_val.returncode ):
    print "RUN failed\n\n%s\n\n" % (errors)
    success = False

if( errors ): log_file.write("\n\n%s\n\n" % errors)

最初我是run_command通过管道tee传输的,以便副本直接进入日志文件,并且流仍然直接输出到终端——但这样我就无法存储任何错误(据我所知)。


到目前为止我的临时解决方案:

ret_val = subprocess.Popen( run_command, stdout=log_file, stderr=subprocess.PIPE, shell=True )
while not ret_val.poll():
    log_file.flush()

然后,在另一个终端中,运行tail -f log.txt(st log_file = 'log.txt')。

4

18 回答 18

211

Python 3 的 TLDR:

import subprocess
import sys
with open('test.log', 'wb') as f: 
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for c in iter(lambda: process.stdout.read(1), b''): 
        sys.stdout.buffer.write(c)
        f.buffer.write(c)

read您有两种方法可以做到这一点,或者通过从orreadline 函数创建迭代器并执行以下操作:

import subprocess
import sys
with open('test.log', 'w') as f:  # replace 'w' with 'wb' for Python 3
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for c in iter(lambda: process.stdout.read(1), ''):  # replace '' with b'' for Python 3
        sys.stdout.write(c)
        f.write(c)

或者

import subprocess
import sys
with open('test.log', 'w') as f:  # replace 'w' with 'wb' for Python 3
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for line in iter(process.stdout.readline, ''):  # replace '' with b'' for Python 3
        sys.stdout.write(line)
        f.write(line)

或者您可以创建一个reader和一个writer文件。将 the传递writer给 thePopen并从 the 中读取reader

import io
import time
import subprocess
import sys

filename = 'test.log'
with io.open(filename, 'wb') as writer, io.open(filename, 'rb', 1) as reader:
    process = subprocess.Popen(command, stdout=writer)
    while process.poll() is None:
        sys.stdout.write(reader.read())
        time.sleep(0.5)
    # Read the remaining
    sys.stdout.write(reader.read())

这样,您将在test.log标准输出和标准输出中写入数据。

文件方法的唯一优点是您的代码不会阻塞。因此,您可以在此期间做任何您想做的事情,并随时以reader非阻塞方式阅读。当您使用PIPE,readreadline函数将阻塞,直到将一个字符写入管道或将一行分别写入管道。

于 2013-08-24T19:23:08.693 回答
107

执行摘要(或“tl;dr”版本):最多只有一个时很容易subprocess.PIPE,否则很难。

可能是时候解释一下它是如何subprocess.Popen工作的了。

(警告:这是针对 Python 2.x 的,虽然 3.x 类似;而且我对 Windows 变体很模糊。我对 POSIX 的理解要好得多。)

Popen函数需要同时处理零到三个 I/O 流。这些标记为stdin, stdout, 和stderr往常一样。

您可以提供:

  • None,表示您不想重定向流。它将像往常一样继承这些。请注意,至少在 POSIX 系统上,这并不意味着它将使用 Python 的sys.stdout,只是 Python 的实际标准输出;见最后的演示。
  • 一个int值。这是一个“原始”文件描述符(至少在 POSIX 中)。(旁注:PIPEandSTDOUT实际上int在内部是 s,但是是“不可能的”描述符,-1 和 -2。)
  • 流——实际上,任何具有fileno方法的对象。 Popen将使用 找到该流的描述符,stream.fileno()然后继续作为int值。
  • subprocess.PIPE,表示 Python 应该创建一个管道。
  • subprocess.STDOUTstderr仅用于):告诉 Python 使用与 for 相同的描述符stdoutNone这仅在您为 提供(非)值时才有意义,即使那样,只有在您设置stdout时才需要stdout=subprocess.PIPE它。(否则,您可以只提供您为 提供的相同参数stdout,例如,Popen(..., stdout=stream, stderr=stream)。)

最简单的情况(没有管道)

如果您什么都不重定向(将所有三个保留为默认None值或提供显式None),Pipe这很容易。它只需要剥离子进程并让它运行。或者,如果您重定向到非PIPE-anint或流的fileno()- 它仍然很容易,因为操作系统会完成所有工作。Python 只需要分离子进程,将其标准输入、标准输出和/或标准错误连接到提供的文件描述符。

仍然很简单的情况:一根管子

如果你只重定向一个流,Pipe事情仍然很容易。让我们一次选择一个流并观看。

假设您想提供一些stdin,但不重定向,stdout或者stderr转到文件描述符。作为父进程,您的 Python 程序只需要使用write()它来通过管道发送数据。您可以自己执行此操作,例如:

proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc

或者您可以将标准输入数据传递给proc.communicate(),然后执行stdin.write如上所示。没有输出返回,因此communicate()只有另一项真正的工作:它还为您关闭管道。(如果您不调用,则proc.communicate()必须调用proc.stdin.close()以关闭管道,以便子进程知道没有更多数据通过。)

假设您想捕捉但独自stdout离开。同样,这很容易:只需调用(或等效的)直到没有更多输出。由于它是一个普通的 Python I/O 流,因此您可以在其上使用所有普通构造,例如:stdinstderrproc.stdout.read()proc.stdout()

for line in proc.stdout:

或者,你可以再次使用proc.communicate(),它只是read()为你做的。

如果您只想捕获stderr,它的工作原理与 with 相同stdout

在事情变得艰难之前,还有一个技巧。假设您要捕获stdout,并且还要捕获stderr在与标准输出相同的管道上:

proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

在这种情况下,subprocess“作弊”!好吧,它必须这样做,所以它并不是真正的作弊:它启动子进程时,它的 stdout 和它的 stderr 都指向(单个)管道描述符,该管道描述符反馈给它的父(Python)进程。在父端,再次只有一个管道描述符用于读取输出。所有“stderr”输出都显示在 中proc.stdout,如果您调用proc.communicate(),stderr 结果(元组中的第二个值)将是None,而不是字符串。

困难的情况:两个或更多的管道

当您想使用至少两个管道时,所有问题都会出现。其实subprocess代码本身就有这么一点:

def communicate(self, input=None):
    ...
    # Optimization: If we are only using one pipe, or no pipe at
    # all, using select() or threads is unnecessary.
    if [self.stdin, self.stdout, self.stderr].count(None) >= 2:

但是,唉,在这里我们至少制作了两个,也许是三个不同的管道,所以count(None)返回 1 或 0。我们必须以艰难的方式做事。

在 Windows 上,这用于threading.Threadself.stdout和累积结果self.stderr,并让父线程传递self.stdin输入数据(然后关闭管道)。

在 POSIX 上,poll如果可用,则使用,否则select,累积输出并提供标准输入输入。所有这些都在(单个)父进程/线程中运行。

这里需要线程或轮询/选择以避免死锁。例如,假设我们已将所有三个流重定向到三个单独的管道。进一步假设在写入过程暂停之前可以将多少数据填充到管道中的限制很小,等待读取过程从另一端“清除”管道。让我们将这个小限制设置为单个字节,只是为了说明。(这实际上是事情的运作方式,除了限制远大于一个字节。)

如果父 (Python) 进程尝试写入多个字节,例如'go\n'to proc.stdin,则第一个字节进入,然后第二个字节导致 Python 进程挂起,等待子进程读取第一个字节,从而清空管道。

同时,假设子进程决定打印一个友好的“Hello!Don't Panic!”。问候。H进入其标准输出管道,但导致e它暂停,等待其父级读取H,清空标准输出管道。

现在我们陷入了困境:Python 进程处于休眠状态,等待说完“go”,而子进程也处于休眠状态,等待说完“Hello!Don't Panic!”。

subprocess.Popen代码通过线程或选择/轮询避免了这个问题。当字节可以通过管道时,它们就会通过。当它们不能时,只有一个线程(而不是整个进程)必须休眠——或者,在选择/轮询的情况下,Python 进程同时等待“可以写入”或“数据可用”,写入进程的标准输入仅当有空间时,并且仅在数据准备好时才读取其标准输出和/或标准错误。一旦发送了所有标准输入数据(如果有)并且所有标准输出和/或标准错误数据都已累积,代码(实际上是处理毛茸茸的情况的地方)返回proc.communicate()_communicate

如果你想同时读取stdout两个stderr不同的管道(不管任何stdin重定向),你也需要避免死锁。这里的死锁场景是不同的——它发生在stderr当你从 中提取数据时子进程写了很长的东西stdout,反之亦然——但它仍然存在。


演示

我承诺证明,未重定向的 Python subprocesses 写入底层标准输出,而不是sys.stdout. 所以,这里有一些代码:

from cStringIO import StringIO
import os
import subprocess
import sys

def show1():
   print 'start show1'
   save = sys.stdout
   sys.stdout = StringIO()
   print 'sys.stdout being buffered'
   proc = subprocess.Popen(['echo', 'hello'])
   proc.wait()
   in_stdout = sys.stdout.getvalue()
   sys.stdout = save
   print 'in buffer:', in_stdout

def show2():
   print 'start show2'
   save = sys.stdout
   sys.stdout = open(os.devnull, 'w')
   print 'after redirect sys.stdout'
   proc = subprocess.Popen(['echo', 'hello'])
   proc.wait()
   sys.stdout = save

show1()
show2()

运行时:

$ python out.py
start show1
hello
in buffer: sys.stdout being buffered

start show2
hello

请注意,如果添加stdout=sys.stdout,第一个例程将失败,因为StringIO对象没有fileno。第二个将省略helloif you add stdout=sys.stdoutsince sys.stdouthas been redirected to os.devnull

(如果您重定向 Python 的 file-descriptor-1,子进程遵循该重定向。该open(os.devnull, 'w')调用会产生一个fileno()大于 2 的流。)

于 2013-08-24T20:51:33.707 回答
26

我们还可以使用默认文件迭代器来读取标准输出,而不是使用带有 readline() 的 iter 构造。

import subprocess
import sys
process = subprocess.Popen(your_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in process.stdout:
    sys.stdout.write(line)
于 2016-07-13T01:37:08.967 回答
16

除了所有这些答案之外,一种简单的方法也可以如下:

process = subprocess.Popen(your_command, stdout=subprocess.PIPE)

while process.stdout.readable():
    line = process.stdout.readline()

    if not line:
        break

    print(line.strip())

只要可读流就循环遍历可读流,如果结果为空,则停止。

这里的关键是只要有输出就readline()返回一行(末尾有),如果它真的在末尾则为空。\n

希望这可以帮助某人。

于 2018-08-30T07:32:01.160 回答
12

如果您能够使用第三方库,您也许可以使用类似的东西sarge(披露:我是它的维护者)。该库允许对子进程的输出流进行非阻塞访问——它位于subprocess模块之上。

于 2013-08-24T22:43:25.853 回答
6

解决方案 1:实时同时记录stdoutANDstderr

一个简单的解决方案,它同时将 stdout 和 stderr实时逐行记​​录到日志文件中。

import subprocess as sp
from concurrent.futures import ThreadPoolExecutor


def log_popen_pipe(p, stdfile):

    with open("mylog.txt", "w") as f:

        while p.poll() is None:
            f.write(stdfile.readline())
            f.flush()

        # Write the rest from the buffer
        f.write(stdfile.read())


with sp.Popen(["ls"], stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    with ThreadPoolExecutor(2) as pool:
        r1 = pool.submit(log_popen_pipe, p, p.stdout)
        r2 = pool.submit(log_popen_pipe, p, p.stderr)
        r1.result()
        r2.result()

解决方案 2:read_popen_pipes()允许您实时同时迭代两个管道 (stdout/stderr)的函数

import subprocess as sp
from queue import Queue, Empty
from concurrent.futures import ThreadPoolExecutor


def enqueue_output(file, queue):
    for line in iter(file.readline, ''):
        queue.put(line)
    file.close()


def read_popen_pipes(p):

    with ThreadPoolExecutor(2) as pool:
        q_stdout, q_stderr = Queue(), Queue()

        pool.submit(enqueue_output, p.stdout, q_stdout)
        pool.submit(enqueue_output, p.stderr, q_stderr)

        while True:

            if p.poll() is not None and q_stdout.empty() and q_stderr.empty():
                break

            out_line = err_line = ''

            try:
                out_line = q_stdout.get_nowait()
                err_line = q_stderr.get_nowait()
            except Empty:
                pass

            yield (out_line, err_line)

# The function in use:

with sp.Popen(["ls"], stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    for out_line, err_line in read_popen_pipes(p):
        print(out_line, end='')
        print(err_line, end='')

    p.poll()

于 2019-07-17T21:42:41.930 回答
4

与以前的答案类似,但以下解决方案适用于我在 Windows 上使用 Python3 提供一种实时打印和登录的常用方法(getting-realtime-output-using-python):

def print_and_log(command, logFile):
    with open(logFile, 'wb') as f:
        command = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)

        while True:
            output = command.stdout.readline()
            if not output and command.poll() is not None:
                f.close()
                break
            if output:
                f.write(output)
                print(str(output.strip(), 'utf-8'), flush=True)
        return command.poll()
于 2018-10-30T11:30:13.010 回答
4

如果您只需要输出将在控制台上可见,对我来说最简单的解决方案是将以下参数传递给Popen

with Popen(cmd, stdout=sys.stdout, stderr=sys.stderr) as proc:

这将使用您的 python 脚本 stdio 文件句柄

于 2021-04-11T08:30:38.087 回答
3

一个好的但“重量级”的解决方案是使用 Twisted - 见底部。

如果您愿意只使用标准输出,那么这些方面的东西应该可以工作:

import subprocess
import sys
popenobj = subprocess.Popen(["ls", "-Rl"], stdout=subprocess.PIPE)
while not popenobj.poll():
   stdoutdata = popenobj.stdout.readline()
   if stdoutdata:
      sys.stdout.write(stdoutdata)
   else:
      break
print "Return code", popenobj.returncode

(如果您使用 read() 它会尝试读取整个“文件”,这是无用的,我们真正可以在这里使用的是读取管道中所有数据的东西)

也可以尝试使用线程来解决这个问题,例如:

import subprocess
import sys
import threading

popenobj = subprocess.Popen("ls", stdout=subprocess.PIPE, shell=True)

def stdoutprocess(o):
   while True:
      stdoutdata = o.stdout.readline()
      if stdoutdata:
         sys.stdout.write(stdoutdata)
      else:
         break

t = threading.Thread(target=stdoutprocess, args=(popenobj,))
t.start()
popenobj.wait()
t.join()
print "Return code", popenobj.returncode

现在我们可以通过拥有两个线程来潜在地添加 stderr。

但是请注意,子进程文档不鼓励直接使用这些文件并建议使用communicate()(主要关注死锁,我认为这不是上面的问题)并且解决方案有点笨拙,所以看起来子进程模块似乎不太适合这项工作(另见: http: //www.python.org/dev/peps/pep-3145/),我们需要看看别的东西。

一个更复杂的解决方案是使用Twisted,如下所示:https ://twistedmatrix.com/documents/11.1.0/core/howto/process.html

使用Twisted执行此操作的方式是使用reactor.spawnprocess()并提供 a创建进程ProcessProtocol,然后异步处理输出。Twisted 示例 Python 代码在这里:https ://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.py

于 2013-08-24T18:53:02.750 回答
3

基于以上所有,我建议稍微修改版本(python3):

  • while 循环调用 readline (建议的 iter 解决方案似乎对我来说永远阻塞 - Python 3,Windows 7)
  • 结构化,因此在 poll 返回后不需要重复对读取数据的处理None
  • stderr 通过管道传输到 stdout,以便读取两个输出输出
  • 添加代码以获取 cmd 的退出值。

代码:

import subprocess
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
                        stderr=subprocess.STDOUT, universal_newlines=True)
while True:
    rd = proc.stdout.readline()
    print(rd, end='')  # and whatever you want to do...
    if not rd:  # EOF
        returncode = proc.poll()
        if returncode is not None:
            break
        time.sleep(0.1)  # cmd closed stdout, but not exited yet

# You may want to check on ReturnCode here
于 2018-12-18T10:07:35.840 回答
2

看起来行缓冲输出对您有用,在这种情况下,可能适合以下内容。(警告:未经测试。)这只会实时提供子进程的标准输出。如果您想实时同时拥有 stderr 和 stdout,则必须使用select.

proc = subprocess.Popen(run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
while proc.poll() is None:
    line = proc.stdout.readline()
    print line
    log_file.write(line + '\n')
# Might still be data on stdout at this point.  Grab any
# remainder.
for line in proc.stdout.read().split('\n'):
    print line
    log_file.write(line + '\n')
# Do whatever you want with proc.stderr here...
于 2013-08-24T19:21:35.450 回答
2

我找到了一个非常复杂的问题的简单解决方案。

  1. stdout 和 stderr 都需要流式传输。
  2. 它们都需要是非阻塞的:当没有输出时和输出太多时。
  3. 不想使用 Threading 或 multiprocessing,也不愿意使用 pexpect。

该解决方案使用我在这里找到的要点

import subprocess as sbp
import fcntl
import os

def non_block_read(output):
    fd = output.fileno()
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
    try:
        return output.readline()
    except:
        return ""

with sbp.Popen('find / -name fdsfjdlsjf',
                shell=True,
                universal_newlines=True,
                encoding='utf-8',
                bufsize=1,
                stdout=sbp.PIPE,
                stderr=sbp.PIPE) as p:
    while True:
        out = non_block_read(p.stdout)
        err = non_block_read(p.stderr)
        if out:
            print(out, end='')
        if err:
            print('E: ' + err, end='')
        if p.poll() is not None:
            break
于 2021-08-21T12:31:20.990 回答
2

我认为该subprocess.communicate方法有点误导:它实际上填充您在subprocess.Popen.

subprocess.PIPE但是,从您可以提供给subprocess.Popenstdoutstderr参数的内容中读取最终会填满操作系统管道缓冲区并使您的应用程序死锁(特别是如果您有多个必须使用的进程/线程subprocess)。

我提出的解决方案是为stdoutstderr提供文件 - 并读取文件的内容而不是从 deadlocking 中读取PIPE。这些文件可以是tempfile.NamedTemporaryFile()- 在它们被写入时也可以访问以进行读取subprocess.communicate

下面是一个示例用法:

        try:
            with ProcessRunner(('python', 'task.py'), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner:
                for out in process_runner:
                    print(out)
        catch ProcessError as e:
            print(e.error_message)
            raise

这是源代码,我可以提供尽可能多的注释来解释它的作用:

如果您使用的是 python 2,请确保首先从 pypi安装最新版本的subprocess32包。


import os
import sys
import threading
import time
import tempfile
import logging

if os.name == 'posix' and sys.version_info[0] < 3:
    # Support python 2
    import subprocess32 as subprocess
else:
    # Get latest and greatest from python 3
    import subprocess

logger = logging.getLogger(__name__)


class ProcessError(Exception):
    """Base exception for errors related to running the process"""


class ProcessTimeout(ProcessError):
    """Error that will be raised when the process execution will exceed a timeout"""


class ProcessRunner(object):
    def __init__(self, args, env=None, timeout=None, bufsize=-1, seconds_to_wait=0.25, **kwargs):
        """
        Constructor facade to subprocess.Popen that receives parameters which are more specifically required for the
        Process Runner. This is a class that should be used as a context manager - and that provides an iterator
        for reading captured output from subprocess.communicate in near realtime.

        Example usage:


        try:
            with ProcessRunner(('python', task_file_path), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner:
                for out in process_runner:
                    print(out)
        catch ProcessError as e:
            print(e.error_message)
            raise

        :param args: same as subprocess.Popen
        :param env: same as subprocess.Popen
        :param timeout: same as subprocess.communicate
        :param bufsize: same as subprocess.Popen
        :param seconds_to_wait: time to wait between each readline from the temporary file
        :param kwargs: same as subprocess.Popen
        """
        self._seconds_to_wait = seconds_to_wait
        self._process_has_timed_out = False
        self._timeout = timeout
        self._process_done = False
        self._std_file_handle = tempfile.NamedTemporaryFile()
        self._process = subprocess.Popen(args, env=env, bufsize=bufsize,
                                         stdout=self._std_file_handle, stderr=self._std_file_handle, **kwargs)
        self._thread = threading.Thread(target=self._run_process)
        self._thread.daemon = True

    def __enter__(self):
        self._thread.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self._thread.join()
        self._std_file_handle.close()

    def __iter__(self):
        # read all output from stdout file that subprocess.communicate fills
        with open(self._std_file_handle.name, 'r') as stdout:
            # while process is alive, keep reading data
            while not self._process_done:
                out = stdout.readline()
                out_without_trailing_whitespaces = out.rstrip()
                if out_without_trailing_whitespaces:
                    # yield stdout data without trailing \n
                    yield out_without_trailing_whitespaces
                else:
                    # if there is nothing to read, then please wait a tiny little bit
                    time.sleep(self._seconds_to_wait)

            # this is a hack: terraform seems to write to buffer after process has finished
            out = stdout.read()
            if out:
                yield out

        if self._process_has_timed_out:
            raise ProcessTimeout('Process has timed out')

        if self._process.returncode != 0:
            raise ProcessError('Process has failed')

    def _run_process(self):
        try:
            # Start gathering information (stdout and stderr) from the opened process
            self._process.communicate(timeout=self._timeout)
            # Graceful termination of the opened process
            self._process.terminate()
        except subprocess.TimeoutExpired:
            self._process_has_timed_out = True
            # Force termination of the opened process
            self._process.kill()

        self._process_done = True

    @property
    def return_code(self):
        return self._process.returncode



于 2019-02-25T14:46:36.660 回答
2

我尝试的所有上述解决方案都未能分离 stderr 和 stdout 输出(多个管道)或当操作系统管道缓冲区已满时永远阻塞,当您运行的命令输出太快时会发生这种情况(在 python 上有一个警告) poll() 子进程手册)。我发现唯一可靠的方法是通过选择,但这是一个仅限 posix 的解决方案:

import subprocess
import sys
import os
import select
# returns command exit status, stdout text, stderr text
# rtoutput: show realtime output while running
def run_script(cmd,rtoutput=0):
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    poller = select.poll()
    poller.register(p.stdout, select.POLLIN)
    poller.register(p.stderr, select.POLLIN)

    coutput=''
    cerror=''
    fdhup={}
    fdhup[p.stdout.fileno()]=0
    fdhup[p.stderr.fileno()]=0
    while sum(fdhup.values()) < len(fdhup):
        try:
            r = poller.poll(1)
        except select.error, err:
            if err.args[0] != EINTR:
                raise
            r=[]
        for fd, flags in r:
            if flags & (select.POLLIN | select.POLLPRI):
                c = os.read(fd, 1024)
                if rtoutput:
                    sys.stdout.write(c)
                    sys.stdout.flush()
                if fd == p.stderr.fileno():
                    cerror+=c
                else:
                    coutput+=c
            else:
                fdhup[fd]=1
    return p.poll(), coutput.strip(), cerror.strip()
于 2017-05-25T14:25:16.550 回答
2

为什么不stdout直接设置为sys.stdout?如果你还需要输出到日志,那么你可以简单地覆盖 f 的 write 方法。

import sys
import subprocess

class SuperFile(open.__class__):

    def write(self, data):
        sys.stdout.write(data)
        super(SuperFile, self).write(data)

f = SuperFile("log.txt","w+")       
process = subprocess.Popen(command, stdout=f, stderr=f)
于 2017-02-04T19:38:10.957 回答
1

这是我在我的一个项目中使用的一个类。它将子进程的输出重定向到日志。起初我尝试简单地覆盖写方法,但这不起作用,因为子进程永远不会调用它(重定向发生在文件描述符级别)。所以我使用我自己的管道,类似于它在子进程模块中的完成方式。这具有将所有日志记录/打印逻辑封装在适配器中的优点,您可以简单地将记录器的实例传递给Popensubprocess.Popen("/path/to/binary", stderr = LogAdapter("foo"))

class LogAdapter(threading.Thread):

    def __init__(self, logname, level = logging.INFO):
        super().__init__()
        self.log = logging.getLogger(logname)
        self.readpipe, self.writepipe = os.pipe()

        logFunctions = {
            logging.DEBUG: self.log.debug,
            logging.INFO: self.log.info,
            logging.WARN: self.log.warn,
            logging.ERROR: self.log.warn,
        }

        try:
            self.logFunction = logFunctions[level]
        except KeyError:
            self.logFunction = self.log.info

    def fileno(self):
        #when fileno is called this indicates the subprocess is about to fork => start thread
        self.start()
        return self.writepipe

    def finished(self):
       """If the write-filedescriptor is not closed this thread will
       prevent the whole program from exiting. You can use this method
       to clean up after the subprocess has terminated."""
       os.close(self.writepipe)

    def run(self):
        inputFile = os.fdopen(self.readpipe)

        while True:
            line = inputFile.readline()

            if len(line) == 0:
                #no new data was added
                break

            self.logFunction(line.strip())

如果您不需要记录但只是想使用print(),您显然可以删除大部分代码并保持类更短。您还可以通过__enter__and__exit__方法对其进行扩展并调用finished__exit__以便您可以轻松地将其用作上下文。

于 2017-05-09T14:21:49.753 回答
1

没有一个 Pythonic 解决方案对我有用。事实证明,proc.stdout.read()或类似的可能会永远阻塞。

因此,我tee这样使用:

subprocess.run('./my_long_running_binary 2>&1 | tee -a my_log_file.txt && exit ${PIPESTATUS}', shell=True, check=True, executable='/bin/bash')

如果您已经在使用shell=True.

${PIPESTATUS}捕获整个命令链的成功状态(仅在 Bash 中可用)。如果我省略了&& exit ${PIPESTATUS},那么这将始终返回零,因为tee永远不会失败。

unbuffer可能需要将每一行立即打印到终端中,而不是等待太久直到“管道缓冲区”被填满。但是,unbuffer 吞下了 assert (SIG Abort) 的退出状态...

2>&1还将 stderror 记录到文件中。

于 2019-04-04T17:02:02.227 回答
0
import os

def execute(cmd, callback):
    for line in iter(os.popen(cmd).readline, ''): 
            callback(line[:-1])

execute('ls -a', print)
于 2020-09-25T06:54:05.597 回答