подпроцесс реального времени. Открыть через стандартный вывод и ТРУБУ - PullRequest
18 голосов
/ 18 января 2010

Я пытаюсь получить stdout от subprocess.Popen вызова, и хотя я легко достигаю этого, выполнив:

cmd = subprocess.Popen('ls -l', shell=True, stdout=PIPE)
for line in cmd.stdout.readlines():
    print line

Я бы хотел получить stdout в режиме реального времени. С помощью описанного выше метода PIPE ждет, чтобы захватить все stdout, а затем возвращает.

Так что для целей ведения журнала это не соответствует моим требованиям (например, "посмотреть", что происходит, пока это происходит).

Есть ли способ получить построчно, stdout во время работы? Или это ограничение subprocess (нужно дождаться закрытия PIPE).

EDIT Если я переключу readlines() на readline(), я получу только последнюю строку stdout (не идеально):

In [75]: cmd = Popen('ls -l', shell=True, stdout=PIPE)
In [76]: for i in cmd.stdout.readline(): print i
....: 
t
o
t
a
l

1
0
4

Ответы [ 8 ]

19 голосов
/ 27 февраля 2010

Ваш переводчик буферизует. Добавьте вызов sys.stdout.flush () после вашего оператора печати. ​​

12 голосов
/ 09 июня 2014

На самом деле реальным решением является прямое перенаправление stdout подпроцесса на stdout вашего процесса.

Действительно, с вашим решением вы можете печатать только stdout, а не stderr, например, одновременно.

import sys
from subprocess import Popen
Popen("./slow_cmd_output.sh", stdout=sys.stdout, stderr=sys.stderr).communicate()

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

Таким образом, например, вы перенаправляете как stdout, так и stderr, и в абсолютном реальном времени.

Например, в моем случае я тестировал этот скрипт slow_cmd_output.sh:

#!/bin/bash

for i in 1 2 3 4 5 6; do sleep 5 && echo "${i}th output" && echo "err output num ${i}" >&2; done
11 голосов
/ 18 января 2010

Чтобы получить вывод «в режиме реального времени», subprocess не подходит, потому что он не может победить стратегии буферизации другого процесса. По этой причине я всегда рекомендую, когда требуется захват данных в режиме реального времени (довольно частый вопрос о переполнении стека!), Вместо него использовать pexpect (везде, кроме Windows - в Windows wexpect ).

3 голосов
/ 06 мая 2013

Поскольку это вопрос, на который я искал ответ в течение нескольких дней, я хотел оставить это здесь для тех, кто следует. Хотя верно, что subprocess не может бороться со стратегией буферизации другого процесса, в случае, когда вы вызываете другой скрипт Python с subprocess.Popen, вы МОЖЕТЕ сказать ему запустить небуферизованный питон.

command = ["python", "-u", "python_file.py"]
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

for line in iter(p.stdout.readline, ''):
    line = line.replace('\r', '').replace('\n', '')
    print line
    sys.stdout.flush()

Я также видел случаи, когда всплывающие аргументы bufsize=1 и universal_newlines=True помогли разоблачить скрытое stdout.

3 голосов
/ 18 января 2010

Удалите readlines (), который объединяет вывод. Также вам нужно будет применить буферизацию строки, так как большинство команд будут буферизовать вывод в канал. Подробнее см .: http://www.pixelbeat.org/programming/stdio_buffering/

1 голос
/ 18 января 2010
cmd = subprocess.Popen(["ls", "-l"], stdout=subprocess.PIPE)
for line in cmd.stdout:
    print line.rstrip("\n")
0 голосов
/ 12 сентября 2015

Как уже говорилось, проблема в буферизации библиотеки stdio операторов типа printf, когда к процессу не подключен ни один терминал. В любом случае на платформе Windows есть способ обойти это. Аналогичное решение может быть и на других платформах.

В Windows вы можете принудительно создать новую консоль при создании процесса. Хорошо, что это может оставаться скрытым, поэтому вы никогда его не увидите (это делается с помощью shell = True внутри модуля подпроцесса).

cmd = subprocess.Popen('ls -l', shell=True, stdout=PIPE, creationflags=_winapi.CREATE_NEW_CONSOLE, bufsize=1, universal_newlines=True)
for line in cmd.stdout.readlines():
    print line

или

Несколько более полное решение состоит в том, что вы явно устанавливаете параметры STARTUPINFO, которые предотвращают запуск нового и ненужного процесса оболочки cmd.exe, для которого shell = True сделал выше.

class PopenBackground(subprocess.Popen):
    def __init__(self, *args, **kwargs):

        si = kwargs.get('startupinfo', subprocess.STARTUPINFO())
        si.dwFlags |= _winapi.STARTF_USESHOWWINDOW
        si.wShowWindow = _winapi.SW_HIDE

        kwargs['startupinfo'] = si
        kwargs['creationflags'] = kwargs.get('creationflags', 0) | _winapi.CREATE_NEW_CONSOLE
        kwargs['bufsize'] = 1
        kwargs['universal_newlines'] = True

        super(PopenBackground, self).__init__(*args, **kwargs)

process = PopenBackground(['ls', '-l'], stdout=subprocess.PIPE)
    for line in cmd.stdout.readlines():
        print line
0 голосов
/ 18 января 2010

Звонок на readlines ожидает завершения процесса.Замените это на цикл вокруг cmd.stdout.readline() (примечание единственного числа), и все должно быть хорошо.

...