Получение вывода в реальном времени с использованием подпроцесса - PullRequest
118 голосов
/ 29 апреля 2009

Я пытаюсь написать скрипт-обертку для программы командной строки (проверка svnadmin), которая будет отображать хороший индикатор выполнения операции. Это требует, чтобы я мог видеть каждую строку вывода из обернутой программы, как только она выводится.

Я подумал, что я просто запустил бы программу, используя 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*)?

Ответы [ 15 ]

76 голосов
/ 29 апреля 2009

Я попробовал это, и по какой-то причине пока код

for line in p.stdout:
  ...

агрессивно буферизирует, вариант

while True:
  line = p.stdout.readline()
  if not line: break
  ...

нет. По-видимому, это известная ошибка: http://bugs.python.org/issue3907 (по состоянию на 29 августа 2018 года проблема закрыта)

35 голосов
/ 20 июня 2011
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=1)
for line in iter(p.stdout.readline, b''):
    print line,
p.stdout.close()
p.wait()
18 голосов
/ 29 апреля 2009

Вы можете попробовать это:

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, в некоторых случаях входное сообщение не печатается. Попробуйте с помощью команды, которая требует встроенного ввода, и убедитесь сами.

16 голосов
/ 21 февраля 2018

Вы можете напрямую направлять вывод подпроцесса в потоки. Упрощенный пример:

subprocess.run(['ls'], stderr=sys.stderr, stdout=sys.stdout)
3 голосов
/ 29 апреля 2009

Я столкнулся с той же проблемой некоторое время назад. Мое решение состояло в том, чтобы отказаться от итерации для метода read, который немедленно вернется, даже если ваш подпроцесс не завершен, и т. Д.

2 голосов
/ 15 ноября 2018

Stdin и stdout потокового подпроцесса * 1001 с asyncio в Python сообщение в блоге Kevin McCarthy показывает, как это сделать с 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())
2 голосов
/ 17 октября 2018

В зависимости от варианта использования вы также можете отключить буферизацию в самом подпроцессе.

Если подпроцесс будет процессом Python, вы можете сделать это до вызова:

os.environ["PYTHONUNBUFFERED"] = "1"

Или, в качестве альтернативы, передать это в аргументе env на Popen.

В противном случае, если вы работаете в Linux / Unix, вы можете использовать инструмент stdbuf. Например. как:

cmd = ["stdbuf", "-oL"] + cmd

См. Также здесь о stdbuf или других параметрах.

(См. Также здесь для того же ответа.)

2 голосов
/ 22 июня 2017

Вы можете использовать итератор для каждого байта в выходных данных подпроцесса. Это позволяет встроенное обновление (строки, заканчивающиеся на '\ 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.")
2 голосов
/ 13 мая 2015

Решена проблема с выходом в реальном времени: Я сталкивался с подобной проблемой в 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

Надеюсь, это поможет.

~ Сайрам

1 голос
/ 03 августа 2016

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