逐行读取子进程标准输出

def*_*ode 216 python subprocess

我的python脚本使用subprocess来调用非常嘈杂的linux实用程序.我想将所有输出存储到日志文件中并向用户显示一些输出.我认为以下内容可行,但在实用程序产生大量输出之前,输出不会显示在我的应用程序中.

#fake_utility.py, just generates lots of output over time
import time
i = 0
while True:
   print hex(i)*512
   i += 1
   time.sleep(0.5)

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
for line in proc.stdout:
   #the real code does filtering here
   print "test:", line.rstrip()
Run Code Online (Sandbox Code Playgroud)

我真正想要的行为是过滤器脚本在从子进程接收时打印每一行.Sorta就像tee使用python代码一样.

我错过了什么?这甚至可能吗?


更新:

如果将a sys.stdout.flush()添加到fake_utility.py,则代码在python 3.1中具有所需的行为.我正在使用python 2.6.您会认为使用proc.stdout.xreadlines()将与py3k一样工作,但事实并非如此.


更新2:

这是最小的工作代码.

#fake_utility.py, just generates lots of output over time
import sys, time
for i in range(10):
   print i
   sys.stdout.flush()
   time.sleep(0.5)

#display out put line by line
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
#works in python 3.0+
#for line in proc.stdout:
for line in iter(proc.stdout.readline,''):
   print line.rstrip()
Run Code Online (Sandbox Code Playgroud)

Rôm*_*con 168

自从我上次使用Python以来已经很长时间了,但我认为问题在于语句for line in proc.stdout,它在迭代之前读取整个输入.解决方案是使用readline():

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
while True:
  line = proc.stdout.readline()
  if not line:
    break
  #the real code does filtering here
  print "test:", line.rstrip()
Run Code Online (Sandbox Code Playgroud)

当然,你仍然需要处理子进程的缓冲.

注意:根据文档,使用迭代器的解决方案应该等效于使用readline(),除了预读缓冲区,但(或者正因为如此)建议的更改确实为我产生了不同的结果(Windows XP上的Python 2.5).

  • @naxa:for pipes:`for line in iter(proc.stdout.readline,''):`. (13认同)
  • 对于`file.readline()`vs`for line in file`,请参阅http://bugs.python.org/issue3907(简而言之:它适用于Python3;在Python 2.6+上使用`io.open()`) (11认同)
  • 根据PEP 8(http://www.python.org/dev/peps/pep-0008/)中的"编程建议",对EOF的更多pythonic测试将是'if not line:'. (4认同)
  • @ Jan-PhilipGehrcke:是的.1.你可以在Python 3上使用`for line in proc.stdout`(没有预读错误)2.Python 3上的`''!= b''` - 不要复制粘贴代码盲目地 - 想想它的作用和运作方式. (3认同)
  • 我建议在中断之前添加`sys.stdout.flush()`,否则事情会混乱。 (3认同)
  • @JFSebastian:你在Python3上尝试过这个解决方案吗?我的代码之前使用 `iter(proc.stdout.readline, '')` 方法在 Python 2(.7) 上运行,现在我切换到 Python 3.4,代码变成了梨形,循环不返回RAM 使用量在 0 到 3 GB 之间波动。 (2认同)
  • @JFSebastian:当然,`iter(f.readline, b'')` 解决方案相当明显(如果有人感兴趣,也适用于 Python 2)。我评论的重点不是责怪你的解决方案(对不起,如果它看起来像那样,我现在也读到了!),而是描述症状的程度,在这种情况下非常严重(大多数 Py2/ 3个问题导致异常,而这里一个良性循环变成了无限循环,垃圾收集努力对抗新创建对象的泛滥,产生长时间和大振幅的内存使用振荡)。 (2认同)
  • @JasonMock `if not line:` 也会在第一个空行处中断(不一定在流的末尾)。`if line is not None:` 应该可以正常工作。 (2认同)

jbg*_*jbg 37

派对迟到了,但很惊讶没有看到我认为这里最简单的解决方案:

import io
import subprocess

proc = subprocess.Popen(["prog", "arg"], stdout=subprocess.PIPE)
for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):  # or another encoding
    # do something with line
Run Code Online (Sandbox Code Playgroud)

  • 我想使用这个答案,但我得到:`AttributeError:'file'对象没有属性'可读'`py2.7 (24认同)
  • @sorin 这些事情都不使它“无效”。如果您正在编写仍需要支持 Python 2 的库,则不要使用此代码。但许多人有幸能够使用十多年前最近发布的软件。如果您尝试读取已关闭的文件,无论您是否使用“TextIOWrapper”,您都会遇到该异常。您可以简单地处理异常。 (8认同)
  • 适用于python 3 (2认同)
  • 你可能迟到了,但你的答案是最新的 Python 版本,ty (2认同)
  • @Ammad `\n` 是换行符。在 Python 中,按行分割时不会删除换行符,这是惯例 - 如果您迭代文件的行或使用“readlines()”方法,您将看到相同的行为。您只需使用“line[:-1]”即可获得没有它的行(TextIOWrapper 默认情况下以“通用换行符”模式运行,因此即使您在 Windows 上并且该行以“\r\n”结尾,您末尾只有 `\n`,所以 `-1` 有效)。如果您不介意行尾的任何其他类似空白的字符也被删除,您也可以使用“line.rstrip()”。 (2认同)
  • 我在 python 3.7 上收到“AttributeError: 'file' object has no attribute 'readed'”,但这是因为我使用的是“subprocess.run”而不是“subprocess.Popen”。 (2认同)

Ste*_*ter 17

实际上,如果您整理了迭代器,那么缓冲现在可能是您的问题.您可以告诉子进程中的python不要缓冲其输出.

proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
Run Code Online (Sandbox Code Playgroud)

proc = subprocess.Popen(['python','-u', 'fake_utility.py'],stdout=subprocess.PIPE)
Run Code Online (Sandbox Code Playgroud)

从python中调用python时我需要这个.


use*_*134 14

您想要将这些额外参数传递给subprocess.Popen:

bufsize=1, universal_newlines=True
Run Code Online (Sandbox Code Playgroud)

然后你可以像你的例子一样迭代.(使用Python 3.5测试)

  • @nicoulaj如果使用subprocess32包它应该工作. (2认同)

Rot*_*eti 10

一个允许实时、逐行迭代stdoutstderr同时迭代的函数

如果你需要得到输出流两种stdout,并stderr在同一时间,你可以使用下面的函数。

该函数使用队列将两个 Popen 管道合并为一个迭代器。

在这里我们创建函数read_popen_pipes()

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()
            except Empty:
                pass
            try:
                err_line = q_stderr.get_nowait()
            except Empty:
                pass

            yield (out_line, err_line)
Run Code Online (Sandbox Code Playgroud)

read_popen_pipes() 正在使用:

import subprocess as sp


with sp.Popen(my_cmd, stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    for out_line, err_line in read_popen_pipes(p):

        # Do stuff with each line, e.g.:
        print(out_line, end='')
        print(err_line, end='')

    return p.poll() # return status-code
Run Code Online (Sandbox Code Playgroud)


wim*_*wim 8

自 2010 年以来,该subprocess模块已经取得了长足的进步,这里的大多数答案都已经过时了。

这是适用于现代 Python 版本的简单方法:

from subprocess import Popen, PIPE, STDOUT

with Popen(args, stdout=PIPE, stderr=STDOUT, text=True) as proc:
    for line in proc.stdout:
        print(line)
rc = proc.returncode
Run Code Online (Sandbox Code Playgroud)

关于用作Popen上下文管理器(自 Python 3.2 起支持):在with块退出时,标准文件描述符将关闭,并且进程将等待/设置 returncode 属性。请参阅subprocess.py:Popen.__exit__CPython 源代码。


Aiv*_*ven 6

您还可以读取不带循环的行。适用于 python3.6。

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
list_of_byte_strings = process.stdout.readlines()
Run Code Online (Sandbox Code Playgroud)

  • @ndtreviv,如果您希望输出为字符串,则可以将 text=True 传递给 Popen 或使用其“编码”kwarg,无需自行转换 (3认同)

Ste*_*anQ 5

Pythont 3.5在模块中添加了方法run()和,两者都返回一个对象。有了这个,你就可以使用:call()subprocessCompletedProcessproc.stdout.splitlines()

proc = subprocess.run( comman, shell=True, capture_output=True, text=True, check=True )
for line in proc.stdout.splitlines():
   print "stdout:", line
Run Code Online (Sandbox Code Playgroud)

另请参阅如何使用子进程运行方法在 Python 中执行 Shell 命令

  • 该解决方案简短而有效。与最初的问题相比,有一个问题:它不会“收到时”打印每一行,我认为这意味着实时打印消息,就像直接在命令行中运行该进程一样。相反,它仅在进程完成运行后打印输出。 (8认同)
  • 感谢@sfuqua 提到这一点。我广泛使用管道并依赖流数据,并且由于其简洁性而错误地选择了它。 (2认同)