читать подпроцесс stdout построчно - PullRequest
201 голосов
/ 10 мая 2010

Мой скрипт на 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()

Мне действительно нужно, чтобы скрипт фильтра печатал каждую строку по мере ее поступления из подпроцесса. Что-то вроде того, что делает tee, но с кодом Python.

Чего мне не хватает? Это вообще возможно?


Обновление:

Если в fake_utility.py добавлен sys.stdout.flush(), код будет иметь желаемое поведение в 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()

Ответы [ 7 ]

161 голосов
/ 11 мая 2010

Прошло много времени с тех пор, как я в последний раз работал с 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(), за исключением буфера упреждающего чтения, но (или именно из-за этого) предлагаемое изменение действительно привело к разные результаты для меня (Python 2.5 на Windows XP).

36 голосов
/ 22 января 2016

Немного опоздал на вечеринку, но был удивлен, не увидев, как мне кажется, самое простое решение:

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
17 голосов
/ 29 августа 2014

Действительно, если вы разобрались с итератором, буферизация теперь может быть вашей проблемой Вы можете указать питону в подпроцессе не буферизировать его вывод.

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

становится

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

Мне это нужно было при вызове python из питона.

13 голосов
/ 16 октября 2015

Вы хотите передать эти дополнительные параметры subprocess.Popen:

bufsize=1, universal_newlines=True

Затем вы можете выполнить итерацию, как в вашем примере. (Протестировано с Python 3.5)

1 голос
/ 28 декабря 2018

Вы также можете читать строки без цикла. Работает в python3.6.

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
list_of_byte_strings = process.stdout.readlines()
1 голос
/ 21 января 2018

Я попробовал это с python3, и это сработало, source

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 голос
/ 02 апреля 2017

Следующая модификация ответа Ромуло работает для меня на 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...