Получение вывода в реальном времени с помощью подпроцесса
Я пытаюсь написать сценарий оболочки для программы командной строки (svnadmin verify), который будет отображать хороший индикатор выполнения операции. Это требует, чтобы я мог видеть каждую строку вывода из обернутой программы, как только она будет выведена.
я решил, что просто выполню программу с помощью subprocess.Popen используйте stdout=PIPE, затем прочитайте каждую строку по мере ее поступления и действуйте соответственно. Однако, когда я запустил следующий код, выход оказался буферизованным где-то, заставляя его появляться в двух кусках, строки с 1 по 332, затем с 333 по 439 (последняя строка вывода)
from subprocess import Popen, PIPE, STDOUT
p = Popen('svnadmin verify /var/svn/repos/config', stdout = PIPE,
stderr = STDOUT, shell = True)
for line in p.stdout:
print line.replace('n', '')
немного просмотрев документацию по подпроцессу, я обнаружил до Popen, поэтому я попытался установить bufsize в 1 (буфер каждой строки) и 0 (без буфера), но ни одно из значений не изменило способ доставки строк.
в этот момент я начинаю хвататься за соломинку, поэтому я написал следующий вывод петля:
while True:
try:
print p.stdout.next().replace('n', '')
except StopIteration:
break
, но получил тот же результат.
можно ли получить вывод программы "в реальном времени" программы, выполняемой с помощью подпроцесса? Есть ли какой-то другой вариант в Python, который совместим с прямой передачей (не exec*)?
12 ответов:
Я пробовал это, и почему-то пока код
for line in p.stdout: ...буфера агрессивно, вариант
while True: line = p.stdout.readline() if not line: break ...нет. По-видимому, это известная ошибка:http://bugs.python.org/issue3907 (вопрос теперь "закрыт"по состоянию на 29 августа 2018 года)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=1) for line in iter(p.stdout.readline, b''): print line, p.stdout.close() p.wait()
вы можете попробовать это:
import subprocess import sys process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) while True: out = process.stdout.read(1) if out == '' and process.poll() != None: break if out != '': sys.stdout.write(out) sys.stdout.flush()Если вы используете readline вместо read, будут некоторые случаи, когда входное сообщение не печатается. Попробуйте это с помощью команды требуется встроенный ввод и убедитесь сами.
вы можете напрямую направлять выходные данные подпроцесса в потоки. Упрощенный пример:
subprocess.run(['ls'], stderr=sys.stderr, stdout=sys.stdout)
я столкнулся с такой же проблемой некоторое время назад. Мое решение было бросить итерацию для
readметод, который будет немедленно возвращен, даже если ваш подпроцесс не завершен, и т. д.
в реальном масштабе времени вопрос выхода разрешенный: Я действительно столкнулся с подобной проблемой в Python, захватывая вывод в реальном времени из программы c. Я добавил:"fflush (stdout);" в моем C код. Это сработало для меня. Вот фрагмент кода
>
#include <stdio.h> void main() { int count = 1; while (1) { printf(" Count %d\n", count++); fflush(stdout); sleep(1); } }>
#!/usr/bin/python import os, sys import subprocess procExe = subprocess.Popen(".//count", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) while procExe.poll() is None: line = procExe.stdout.readline() print("Print:" + line)> Печати: Графа 1 Печать: Графу 2 Печати: Графа 3
надеюсь, что это помогает.
~Сайрам
с помощью pexpect [ http://www.noah.org/wiki/Pexpect ] с неблокирующими линиями чтения эта проблема будет решена. Это связано с тем, что каналы буферизуются, и поэтому выход вашего приложения буферизуется трубой, поэтому вы не можете добраться до этого выхода, пока буфер не заполнится или процесс не умрет.
я использовал это решение для получения вывода в реальном времени на подпроцессе. Этот цикл остановится, как только процесс завершится, оставив необходимость в инструкции break или возможном бесконечном цикле.
sub_process = subprocess.Popen(my_command, close_fds=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) while sub_process.poll() is None: out = sub_process.stdout.read(1) sys.stdout.write(out) sys.stdout.flush()
вы можете использовать итератор над каждым байтом в выходных данных подпроцесса. Это позволяет встроенное обновление (строки, заканчивающиеся на '\r ' перезаписать предыдущую выходную строку) из подпроцесса:
from subprocess import PIPE, Popen command = ["my_command", "-my_arg"] # Open pipe to subprocess subprocess = Popen(command, stdout=PIPE, stderr=PIPE) # read each byte of subprocess while subprocess.poll() is None: for c in iter(lambda: subprocess.stdout.read(1) if subprocess.poll() is None else {}, b''): c = c.decode('ascii') sys.stdout.write(c) sys.stdout.flush() if subprocess.returncode != 0: raise Exception("The subprocess did not terminate correctly.")
Это основной скелет, который я всегда использую для этого. Это позволяет легко реализовать тайм-ауты и способен справиться с неизбежными висячими процессами.
import subprocess import threading import Queue def t_read_stdout(process, queue): """Read from stdout""" for output in iter(process.stdout.readline, b''): queue.put(output) return process = subprocess.Popen(['dir'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, cwd='C:\', shell=True) queue = Queue.Queue() t_stdout = threading.Thread(target=t_read_stdout, args=(process, queue)) t_stdout.daemon = True t_stdout.start() while process.poll() is None or not queue.empty(): try: output = queue.get(timeout=.5) except Queue.Empty: continue if not output: continue print(output), t_stdout.join()
комплексное решение:
import contextlib import subprocess # Unix, Windows and old Macintosh end-of-line newlines = ['\n', '\r\n', '\r'] def unbuffered(proc, stream='stdout'): stream = getattr(proc, stream) with contextlib.closing(stream): while True: out = [] last = stream.read(1) # Don't loop forever if last == '' and proc.poll() is not None: break while last not in newlines: # Don't loop forever if last == '' and proc.poll() is not None: break out.append(last) last = stream.read(1) out = ''.join(out) yield out def example(): cmd = ['ls', '-l', '/'] proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, # Make all end-of-lines '\n' universal_newlines=True, ) for line in unbuffered(proc): print line example()
нашел эту функцию "подключи и играй"здесь. Сработало как по волшебству!
import subprocess def myrun(cmd): """from http://blog.kagesenshi.org/2008/02/teeing-python-subprocesspopen-output.html """ p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) stdout = [] while True: line = p.stdout.readline() stdout.append(line) print line, if line == '' and p.poll() != None: break return ''.join(stdout)
Comments