关于python:逐行读取子进程标准输出

read subprocess stdout line by line

我的python脚本使用子进程调用一个非常嘈杂的Linux实用程序。我想将所有输出存储到一个日志文件中,并向用户显示其中的一些输出。我认为下面的方法可行,但是直到实用程序产生大量输出之后,输出才会显示在我的应用程序中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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()

我真正想要的行为是过滤器脚本在从子进程接收到每一行时打印它。类似于tee所做的,但使用了python代码。

我错过了什么?这是可能的吗?

更新:

如果将sys.stdout.flush()添加到fake_utility.py中,那么代码在python 3.1中具有所需的行为。我使用的是python 2.6。你可能会认为使用proc.stdout.xreadlines()和py3k的效果一样,但事实并非如此。

更新2:

这里是最小的工作代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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()


我上次使用python已经很久了,但我认为问题在于语句for line in proc.stdout,它在迭代之前读取整个输入。解决方案是使用readline()代替:

1
2
3
4
5
6
7
8
9
10
#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
while True:
  line = proc.stdout.readline()
  if line != '':
    #the real code does filtering here
    print"test:", line.rstrip()
  else:
    break

当然,您仍然需要处理子进程的缓冲。

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


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

1
2
3
4
5
6
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


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

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

变成

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

我在从python内部调用python时需要这个。


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

1
bufsize=1, universal_newlines=True

然后可以像在示例中那样迭代。(用python 3.5测试)


我用python3试过了,效果很好,来源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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()

R的以下修改?在python 2和3(2.7.12和3.6.1)上,mulo的答案对我很有用:

1
2
3
4
5
6
7
8
9
10
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


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

1
2
3
4
5
import os
import subprocess

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