оперативный вывод из команды подпроцесса
Я использую скрипт на Python в качестве драйвера для кода гидродинамики. Когда приходит время запускать симуляцию, я использую subprocess.Popen
для запуска кода, сбора выходных данных из stdout
и stderr
в subprocess.PIPE
--- затем я могу распечатать (и сохранить в лог-файл) выходную информацию и проверить, нет ли ошибок. Проблема в том, что я понятия не имею, как продвигается код. Если я запускаю его непосредственно из командной строки, он выдает мне информацию о том, на какой итерации он выполняется, в какое время, каков следующий временной шаг и т.д.
Есть ли способ как сохранить вывод (для ведения журнала и проверки ошибок), так и создать вывод в реальном времени?
Соответствующий раздел моего кода:
ret_val = subprocess.Popen( run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True )
output, errors = ret_val.communicate()
log_file.write(output)
print output
if( ret_val.returncode ):
print "RUN failed\n\n%s\n\n" % (errors)
success = False
if( errors ): log_file.write("\n\n%s\n\n" % errors)
Изначально я передавал run_command
через tee
, чтобы копия отправлялась непосредственно в файл журнала, а поток по-прежнему выводился непосредственно на терминал - но таким образом я не могу сохранить никаких ошибок (насколько я знаю).
Пока что мое временное решение:
ret_val = subprocess.Popen( run_command, stdout=log_file, stderr=subprocess.PIPE, shell=True )
while not ret_val.poll():
log_file.flush()
затем в другом терминале запустите tail -f log.txt
(s.t. log_file = 'log.txt'
).
Переведено автоматически
Ответ 1
TLDR для Python 3:
import subprocess
import sys
with open("test.log", "wb") as f:
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for c in iter(lambda: process.stdout.read(1), b""):
sys.stdout.buffer.write(c)
f.buffer.write(c)
У вас есть два способа сделать это, либо создав итератор из read
или readline
функций и выполнив:
import subprocess
import sys
# replace "w" with "wb" for Python 3
with open("test.log", "w") as f:
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
# replace "" with b'' for Python 3
for c in iter(lambda: process.stdout.read(1), ""):
sys.stdout.write(c)
f.write(c)
или
import subprocess
import sys
# replace "w" with "wb" for Python 3
with open("test.log", "w") as f:
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
# replace "" with b"" for Python 3
for line in iter(process.stdout.readline, ""):
sys.stdout.write(line)
f.write(line)
Или вы можете создать reader
и writer
файл. Передайте writer
в Popen
и прочитайте из reader
import io
import time
import subprocess
import sys
filename = "test.log"
with io.open(filename, "wb") as writer, io.open(filename, "rb", 1) as reader:
process = subprocess.Popen(command, stdout=writer)
while process.poll() is None:
sys.stdout.write(reader.read())
time.sleep(0.5)
# Read the remaining
sys.stdout.write(reader.read())
Таким образом, у вас будут данные, записанные в test.log
так же, как и в стандартном выводе.
Единственное преимущество файлового подхода заключается в том, что ваш код не блокируется. Таким образом, вы можете делать все, что хотите, в то же время и читать, когда захотите, из reader
неблокирующим способом. При использовании PIPE
, read
и readline
функции будут блокироваться до тех пор, пока в канал не будет записан либо один символ, либо строка соответственно.
Ответ 2
subprocess.PIPE
, в противном случае это сложно.Возможно, пришло время немного объяснить, как subprocess.Popen
это работает.
(Предостережение: это для Python 2.x, хотя 3.x похож; и я довольно нечетко разбираюсь в варианте Windows. Я намного лучше разбираюсь в материалах POSIX.)
Popen
Функция должна обрабатывать потоки ввода-вывода от нуля до трех одновременно. Они обозначаются stdin
, stdout
и stderr
как обычно.
Вы можете предоставить:
None
указывает, что вы не хотите перенаправлять поток. Вместо этого он унаследует их, как обычно. Обратите внимание, что, по крайней мере, в системах POSIX это не означает, что он будет использовать Python sys.stdout
, только фактический стандартный вывод Python; смотрите Демонстрацию в конце.int
. Это "необработанный" файловый дескриптор (по крайней мере, в POSIX). (Примечание: PIPE
и STDOUT
на самом деле int
являются s внутри, но являются "невозможными" дескрипторами, -1 и -2.)fileno
методом. Popen
найдет дескриптор для этого потока, используя stream.fileno()
, а затем продолжит работу как для int
значения.subprocess.PIPE
, указывающий, что Python должен создать канал.subprocess.STDOUT
(только для stderr
): скажите Python использовать тот же дескриптор, что и для stdout
. Это имеет смысл только в том случае, если вы указали (не None
) значение для stdout
, и даже тогда оно необходимо только в том случае, если вы установили stdout=subprocess.PIPE
. (В противном случае вы можете просто указать тот же аргумент, который вы указали для stdout
, например, Popen(..., stdout=stream, stderr=stream)
.)Если вы ничего не перенаправляете (оставьте все три None
значения по умолчанию или укажите явно None
), Pipe
сделать это довольно просто. Нужно просто отключить подпроцесс и дать ему запуститься. Или, если вы перенаправляете на не-PIPE
an int
или поток fileno()
— это все равно легко, поскольку ОС выполняет всю работу. Python просто нужно отключить подпроцесс, подключив его stdin, stdout и / или stderr к предоставленным файловым дескрипторам.
Если вы перенаправляете только один поток, Pipe
все еще довольно просто. Давайте выбирать по одному потоку за раз и наблюдать.
Предположим, вы хотите предоставить некоторые stdin
, но пусть stdout
и stderr
не перенаправляются или переходят к файловому дескриптору. Как родительский процесс, ваша программа на Python просто должна использовать write()
для отправки данных по каналу. Вы можете сделать это самостоятельно, например:
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc
или вы можете передать данные stdin в proc.communicate()
, который затем выполняет stdin.write
показанное выше. Выходные данные не возвращаются, поэтому communicate()
есть только одна другая реальная задача: она также закрывает канал для вас. (Если вы не вызываете proc.communicate()
вы должны вызвать proc.stdin.close()
, чтобы закрыть канал, чтобы подпроцесс знал, что данные больше не поступают.)
Предположим, вы хотите захватить, stdout
но оставить stdin
и stderr
в покое. Опять же, это просто: просто вызывайте proc.stdout.read()
(или эквивалентно), пока больше не будет вывода. Поскольку proc.stdout()
это обычный поток ввода-вывода Python, вы можете использовать в нем все обычные конструкции, например:
for line in proc.stdout:
или, опять же, вы можете использовать proc.communicate()
, который просто выполняет read()
за вас.
Если вы хотите только захватывать stderr
, это работает так же, как с stdout
.
Есть еще один трюк, прежде чем все усложнится. Предположим, вы хотите выполнить захват stdout
, а также захват stderr
но в том же канале, что и стандартный вывод:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
В этом случае subprocess
"обманывает"! Что ж, он должен это делать, так что на самом деле это не обман: он запускает подпроцесс как с его stdout, так и с его stderr, направленными в (одиночный) дескриптор канала, который возвращается к его родительскому процессу (Python). На родительской стороне снова есть только один дескриптор канала для чтения выходных данных. Все выходные данные "stderr" отображаются в proc.stdout
, и если вы вызовете proc.communicate()
, результатом stderr (второе значение в кортеже) будет None
, а не строка.
Все проблемы возникают, когда вы хотите использовать как минимум два канала. Фактически, в subprocess
самом коде есть этот бит:
def communicate(self, input=None):
...
# Optimization: If we are only using one pipe, or no pipe at
# all, using select() or threads is unnecessary.
if [self.stdin, self.stdout, self.stderr].count(None) >= 2:
Но, увы, здесь мы создали как минимум два, а может и три разных канала, поэтому count(None)
возвращает либо 1, либо 0. Мы должны действовать сложным способом.
В Windows это используется threading.Thread
для накопления результатов для self.stdout
и self.stderr
, и родительский поток доставляет self.stdin
входные данные (а затем закрывает канал).
В POSIX это использует poll
если доступно, в противном случае select
для накопления выходных данных и доставки входных данных stdin. Все это выполняется в (единственном) родительском процессе / потоке.
Здесь необходимы потоки или poll / select, чтобы избежать взаимоблокировки. Предположим, например, что мы перенаправили все три потока в три отдельных канала. Предположим далее, что существует небольшое ограничение на количество данных, которые могут быть загружены в канал, прежде чем процесс записи будет приостановлен, ожидая, пока процесс чтения "очистит" канал с другого конца. Давайте установим это небольшое ограничение в один байт, просто для иллюстрации. (На самом деле так все и работает, за исключением того, что ограничение намного больше одного байта.)
Если родительский процесс (Python) пытается записать несколько байтов, скажем, 'go\n'
в proc.stdin
, вводится первый байт, а затем второй заставляет процесс Python приостанавливаться, ожидая, пока подпроцесс прочитает первый байт, опустошая канал.
Между тем, предположим, что подпроцесс решает напечатать дружелюбное приветствие "Привет! Не паникуйте!". H
переходит в канал стандартного вывода, но e
заставляет его приостанавливаться, ожидая, пока его родительский файл прочитает это H
, опустошая канал стандартного вывода.
Теперь мы застряли: процесс Python находится в спящем режиме, ожидая, когда закончит говорить "go", и подпроцесс также находится в спящем режиме, ожидая, когда закончит говорить "Hello! Не паникуйте!".
subprocess.Popen
Код позволяет избежать этой проблемы с помощью потоковой передачи или выбора / опроса. Когда байты могут передаваться по каналам, они передаются. Когда они не могут, только поток (не весь процесс) должен переходить в спящий режим - или, в случае выбора / опроса, процесс Python одновременно ожидает "может записать" или "данные доступны", записывает в stdin процесса только тогда, когда есть место, и считывает его stdout и / или stderr только тогда, когда данные готовы. proc.communicate()
Код (фактически _communicate
, в котором обрабатываются сложные случаи) возвращает результат после отправки всех данных stdin (если таковые имеются) и накопления всех данных stdout и / или stderr.
Если вы хотите читать оба stdout
и stderr
по двум разным каналам (независимо от любого stdin
перенаправления), вам также нужно будет избегать взаимоблокировки. Сценарий взаимоблокировки здесь отличается — он возникает, когда подпроцесс записывает что-то длинное в stderr
пока вы извлекаете данные из stdout
, или наоборот — но он все еще существует.
Я обещал продемонстрировать, что без перенаправления Python subprocess
выполняет запись в базовый стандартный вывод, а не sys.stdout
. Итак, вот некоторый код:
from cStringIO import StringIO
import os
import subprocess
import sys
def show1():
print 'start show1'
save = sys.stdout
sys.stdout = StringIO()
print 'sys.stdout being buffered'
proc = subprocess.Popen(['echo', 'hello'])
proc.wait()
in_stdout = sys.stdout.getvalue()
sys.stdout = save
print 'in buffer:', in_stdout
def show2():
print 'start show2'
save = sys.stdout
sys.stdout = open(os.devnull, 'w')
print 'after redirect sys.stdout'
proc = subprocess.Popen(['echo', 'hello'])
proc.wait()
sys.stdout = save
show1()
show2()
При запуске:
$ python out.py
start show1
hello
in buffer: sys.stdout being buffered
start show2
hello
Обратите внимание, что первая процедура завершится неудачей, если вы добавите stdout=sys.stdout
, поскольку у StringIO
объекта нет fileno
. Во втором будет опущен, hello
если вы добавите, stdout=sys.stdout
поскольку sys.stdout
был перенаправлен на os.devnull
.
(Если вы перенаправите файловый дескриптор Python-1, подпроцесс будет следовать этому перенаправлению. open(os.devnull, 'w')
Вызов создает поток, fileno()
размер которого больше 2.)
Ответ 3
Мы также можем использовать итератор файла по умолчанию для чтения стандартного вывода вместо использования iter construct с readline()
.
import subprocess
import sys
process = subprocess.Popen(
your_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
for line in process.stdout:
sys.stdout.write(line)
Ответ 4
В дополнение ко всем этим ответам, один простой подход также может быть следующим:
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
while process.stdout.readable():
line = process.stdout.readline()
if not line:
break
print(line.strip())
Перебирайте читаемый поток до тех пор, пока он доступен для чтения, и если он получает пустой результат, остановитесь.
Ключевым моментом здесь является то, что readline()
возвращает строку (с \n
в конце) до тех пор, пока есть вывод, и пустую, если она действительно находится в конце.
Надеюсь, это кому-то поможет.