逐行读取子流程标准输出


235

我的python脚本使用子进程来调用非常嘈杂的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()

我真正想要的行为是过滤器脚本打印从子流程接收到的每一行。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()

4
您可以使用print line,代替print line.rstrip()(注意:末尾用逗号)。
jfs 2012年


2
更新2指出它适用于python 3.0+,但使用旧的print语句,因此它不适用于python 3.0+。
Rooky

这里列出的答案对我都不起作用,但是stackoverflow.com/questions/5411780/…确实可以!
盒装

Answers:


179

自从我上一次使用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()

当然,您仍然必须处理子进程的缓冲。

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


11
关于file.readline()vs. for line in file参见bugs.python.org/issue3907(简而言之:它适用于Python3;适用io.open()于Python 2.6+)
jfs

5
根据PEP 8(python.org/dev/peps/pep-0008)中的“编程建议”,对EOF的更多pythonic测试将是“如果不是line:”。
杰森·莫克

14
@naxa:用于管道:for line in iter(proc.stdout.readline, ''):
2012年

3
@ Jan-PhilipGehrcke:是的。1.您可以for line in proc.stdout在Python 3上使用(没有预读错误)2. '' != b''在Python 3上-不要盲目地复制粘贴代码-考虑一下它的功能和工作方式。
jfs 2015年

2
@JFSebastian:当然,iter(f.readline, b'')解决方案非常明显(如果有兴趣的话,也可以在Python 2上使用)。我的评论不是要怪您的解决方案(很抱歉,如果出现这种情况,我现在也读过!),而是要描述症状的严重程度,在这种情况下非常严重(大多数Py2 / 3个问题会导致异常,而在此情况下,行为良好的循环将变为无限循环,并且垃圾回收会与新创建的对象的泛滥作斗争,从而导致内存使用周期长且幅度大。
Jan-Philip Gehrcke博士2015年

45

参加聚会有点晚,但是很惊讶没有看到我认为这是最简单的解决方案:

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

(这需要Python3。)


25
我想使用这个答案,但我得到:AttributeError: 'file' object has no attribute 'readable' py2.7
Dan Garthwaite

3
与python 3一起使用
matanster,

显然,由于多种原因py3 / py3兼容性以及出现ValueError的真正风险,该代码无效:在关闭的文件上进行I / O操作
sorin

3
@sorin这些东西都不会使它“无效”。如果您正在编写仍需要支持Python 2的库,请不要使用此代码。但是,许多人拥有能够使用十年前发布的软件的奢侈享受。如果您尝试读取已关闭的文件,则无论使用TextIOWrapper与否,都会得到该异常。您可以简单地处理异常。
jbg

1
您可能参加聚会很晚,但是您的答案是最新的Python版本,例如
Dusan Gligoric

20

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

proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)

变成

proc = subprocess.Popen(['python','-u', 'fake_utility.py'],stdout=subprocess.PIPE)

从python内部调用python时,我需要此功能。


14

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

bufsize=1, universal_newlines=True

然后,您可以像示例中那样进行迭代。(使用Python 3.5测试)


2
@nicoulaj如果使用subprocess32包,它应该可以工作。
Quantum7 '17

4

允许逐行实时地同时迭代stdout和迭代的功能stderr

万一您需要同时获取stdoutstderr在同一时间,你可以使用下面的函数。

该函数使用队列将两个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)

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

2

您也可以读取不带循环的行。在python3.6中工作。

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
list_of_byte_strings = process.stdout.readlines()

1
或转换成字符串:list_of_strings = [x.decode('utf-8').rstrip('\n') for x in iter(process.stdout.readlines())]
ndtreviv '19

1

我试图与python3和它的工作,

def output_reader(proc):
    for line in iter(proc.stdout.readline, b''):
        print('got line: {0}'.format(line.decode('utf-8')), end='')


def main():
    proc = subprocess.Popen(['python', 'fake_utility.py'],
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT)

    t = threading.Thread(target=output_reader, args=(proc,))
    t.start()

    try:
        time.sleep(0.2)
        import time
        i = 0

        while True:
        print (hex(i)*512)
        i += 1
        time.sleep(0.5)
    finally:
        proc.terminate()
        try:
            proc.wait(timeout=0.2)
            print('== subprocess exited with rc =', proc.returncode)
        except subprocess.TimeoutExpired:
            print('subprocess did not terminate in time')
    t.join()

1

Rômulo答案的以下修改对我适用于Python 2和3(2.7.12和3.6.1):

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
while True:
  line = process.stdout.readline()
  if line != '':
    os.write(1, line)
  else:
    break

0

Dunno(已将其添加到子流程模块中),但使用Python 3时,您可以使用proc.stdout.splitlines()

for line in proc.stdout.splitlines():
   print "stdout:", line
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.