Получение результата в реальном времени с использованием подпроцесса
Я пытаюсь написать оболочку script для программы командной строки (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', '')
Несколько раз посмотрев документацию по подпроцессу, я обнаружил параметр bufsize
до Popen
, поэтому я попробовал установить bufsize в 1 (буфера каждой строки) и 0 (без буфера), но ни одно значение не изменилось способ доставки линий.
В этот момент я начал разбираться соломинки, поэтому я написал следующий выходной цикл:
while True:
try:
print p.stdout.next().replace('\n', '')
except StopIteration:
break
но получил тот же результат.
Возможно ли получить выход программы "в реальном времени" программы, выполненной с использованием подпроцесса? Есть ли еще какой-нибудь другой вариант в Python, совместимый с прямой (не exec*
)?
Ответы
Ответ 1
Я попробовал это, и по какой-то причине пока код
for line in p.stdout:
...
агрессивно буферизирует, вариант
while True:
line = p.stdout.readline()
if not line: break
...
не. По-видимому, это известная ошибка: http://bugs.python.org/issue3907 (по состоянию на 29 августа 2018 года проблема закрыта)
Ответ 2
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=1)
for line in iter(p.stdout.readline, b''):
print line,
p.stdout.close()
p.wait()
Ответ 3
Вы можете попробовать следующее:
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 вместо чтения, будут случаи, когда входное сообщение не печатается. Попробуйте это с помощью команды, для которой требуется встроенный ввод и убедитесь сами.
Ответ 4
Вы можете направить вывод подпроцесса непосредственно в потоки. Упрощенный пример:
subprocess.run(['ls'], stderr=sys.stderr, stdout=sys.stdout)
Ответ 5
Я столкнулся с той же проблемой некоторое время назад. Мое решение состояло в том, чтобы повторить итерацию для метода read
, который немедленно вернется, даже если ваш подпроцесс не будет завершен, и т.д.
Ответ 6
Ошибка выхода в реальном времени:
Я столкнулся с подобной проблемой в Python, захватив вывод в реальном времени из c-программы. Я добавил " fflush (stdout);" в моем C-коде. Это сработало для меня. Вот отрезок кода
< < C Программа →
#include <stdio.h>
void main()
{
int count = 1;
while (1)
{
printf(" Count %d\n", count++);
fflush(stdout);
sleep(1);
}
}
< < Программа Python →
#!/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
Надеюсь, что это поможет.
~ Сайрам
Ответ 7
Вы можете использовать итератор над каждым байтом в выводе подпроцесса. Это позволяет встроенное обновление (строки, заканчивающиеся на "\ 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.")
Ответ 8
Использование pexpect [http://www.noah.org/wiki/Pexpect] с неблокирующими линиями чтения разрешит эту проблему. Это связано с тем, что трубы буферизуются, и поэтому ваш вывод приложения буферизуется трубой, поэтому вы не можете добраться до этого вывода до заполнения буфера или процесса.
Ответ 9
Я использовал это решение для получения вывода в реальном времени на подпроцессе. Этот цикл остановится, как только процесс завершится, оставив потребность в инструкции 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()
Ответ 10
Нашел эту функцию "plug-and-play" здесь. Работали как шарм!
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)
Ответ 11
В зависимости от варианта использования вы также можете отключить буферизацию в самом подпроцессе.
Если подпроцесс будет процессом Python, вы можете сделать это до вызова:
os.environ["PYTHONUNBUFFERED"] = "1"
Или, в качестве альтернативы, передайте это в аргументе env
Popen
.
В противном случае, если вы работаете в Linux/Unix, вы можете использовать инструмент stdbuf
. Например, как:
cmd = ["stdbuf", "-oL"] + cmd
Смотрите также здесь о stdbuf
или других опциях.
(Смотрите также здесь для того же ответа.)
Ответ 12
Полное решение:
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()
Ответ 13
Это основной скелет, который я всегда использую для этого. Это облегчает реализацию тайм-аутов и может справиться с неизбежными процессами зависания.
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()
Ответ 14
Подпроцесс Streaming stdin и stdout с asyncio в блоге Python Кевина Маккарти показывает, как это сделать с помощью asyncio:
import asyncio
from asyncio.subprocess import PIPE
from asyncio import create_subprocess_exec
async def _read_stream(stream, callback):
while True:
line = await stream.readline()
if line:
callback(line)
else:
break
async def run(command):
process = await create_subprocess_exec(
*command, stdout=PIPE, stderr=PIPE
)
await asyncio.wait(
[
_read_stream(
process.stdout,
lambda x: print(
"STDOUT: {}".format(x.decode("UTF8"))
),
),
_read_stream(
process.stderr,
lambda x: print(
"STDERR: {}".format(x.decode("UTF8"))
),
),
]
)
await process.wait()
async def main():
await run("docker build -t my-docker-image:latest .")
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Ответ 15
(Это решение было протестировано с Python 2.7.15)
Вам просто нужно sys.stdout.flush() после каждой строки чтения/записи:
while proc.poll() is None:
line = proc.stdout.readline()
sys.stdout.write(line)
# or print(line.strip()), you still need to force the flush.
sys.stdout.flush()
Ответ 16
Это то, что я сделал:
def read_command_output(command):
process = subprocess.Popen(command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
return iter(process.stdout.readline, b"")
for output_line in read_command_output(cmd):
print(line)
Он непрерывно печатает новые строки, поскольку они выводятся подпроцессом.