Ответ 1
Ваш интерпретатор буферизуется. Добавьте вызов в sys.stdout.flush() после вашего оператора печати.
Я пытаюсь захватить 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
).
ИЗМЕНИТЬ
Если я переключу 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
Ваш интерпретатор буферизуется. Добавьте вызов в sys.stdout.flush() после вашего оператора печати.
На самом деле, реальное решение состоит в том, чтобы напрямую перенаправить stdout подпроцесса на стандартный вывод вашего процесса.
В самом деле, с вашим решением вы можете печатать только stdout, а не stderr, например, в то же время.
import sys
from subprocess import Popen
Popen("./slow_cmd_output.sh", stdout=sys.stdout, stderr=sys.stderr).communicate()
communicate()
заключается в том, чтобы блокировать вызов до конца подпроцесса, иначе он будет напрямую перейти к следующей строке, и ваша программа может завершиться перед подпроцессом (хотя перенаправление на ваш stdout будет работать, даже после закрытия вашего python script, я его протестировал).
Таким образом, например, вы перенаправляете как stdout, так и stderr, и в абсолютном реальном времени.
Например, в моем случае я тестировал этот script 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
Чтобы получить вывод "в реальном времени", subprocess
не подходит, поскольку он не может победить другие стратегии буферизации процессов. Это причина, по которой я всегда рекомендую, всякий раз, когда требуется захват вывода "реального времени" (довольно частый вопрос о переполнении стека!), Вместо этого использовать pexpect (везде, кроме Windows - в Windows, wexpect).
Отбросьте readlines(), который объединяет вывод. Кроме того, вам потребуется принудительно настроить буферизацию строк, поскольку большинство команд будут взаимно буферизовать вывод в канал. Подробнее см.: http://www.pixelbeat.org/programming/stdio_buffering/
Поскольку это вопрос, который я искал в течение нескольких дней, я хотел оставить это здесь для тех, кто следует. Хотя верно, что subprocess
не может бороться с другой стратегией буферизации процессов, в случае, когда вы вызываете другой Python script с 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()
Я также видел случаи, когда аргументы popen bufsize=1
и universal_newlines=True
помогли разоблачить скрытый stdout
.
cmd = subprocess.Popen(["ls", "-l"], stdout=subprocess.PIPE)
for line in cmd.stdout:
print line.rstrip("\n")
Вызов readlines
ожидает завершения процесса. Замените это петлей вокруг cmd.stdout.readline()
(примечание сингулярно), и все должно быть хорошо.
Как уже говорилось, проблема заключается в буферизации библиотеки stdio операторов printf, когда терминал не подключен к процессу. Так или иначе, на платформе Windows есть способ. На других платформах может быть аналогичное решение.
В Windows вы можете принудительно создать новую консоль при создании процесса. Хорошо, что это может оставаться скрытым, чтобы вы никогда его не видели (это выполняется оболочкой = 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