Читать подпроцесс stdout по очереди
Мой python script использует подпроцесс для вызова утилиты 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()
Поведение, которое я действительно хочу, - это фильтр script для печати каждой строки по мере ее получения из подпроцесса. Сорта похожа на то, что tee
, но с кодом python.
Что мне не хватает? Возможно ли это?
Update:
Если 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()
Ответы
Ответ 1
Прошло много времени с тех пор, как я в последний раз работал с 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).
Ответ 2
Бит поздно на вечеринку, но был удивлен, не увидев, что я считаю самым простым решением здесь:
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
Ответ 3
В самом деле, если вы отсортировали итератор, тогда буферизация теперь может быть вашей проблемой. Вы можете сказать python в подпроцессе, чтобы не буферировать его вывод.
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
становится
proc = subprocess.Popen(['python','-u', 'fake_utility.py'],stdout=subprocess.PIPE)
Мне нужно было это при вызове python из python.
Ответ 4
Вы хотите передать эти дополнительные параметры subprocess.Popen
:
bufsize=1, universal_newlines=True
Затем вы можете перебирать как в своем примере. (Протестировано с помощью Python 3.5)
Ответ 5
Следующая модификация ответа Ромуло работает для меня на 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
Ответ 6
Я попробовал это с 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()
Ответ 7
Вы также можете читать строки без цикла. Работает в python3.6.
import os
import subprocess
process = subprocess.Popen(command, stdout=subprocess.PIPE)
list_of_byte_strings = process.stdout.readlines()
Ответ 8
Функция, позволяющая выполнять итерации одновременно stdout
и stderr
в режиме реального времени построчно
Если вам нужно получить выходной поток одновременно для 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)
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