Я использую скрипт 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
(st log_file = 'log.txt'
).
источник
Popen.poll
как в предыдущем вопросе переполнения стека .git
), делают это только в том случае, если их вывод является «tty device» (протестировано через libcisatty()
). В этом случае вам, возможно, придется открыть псевдо-tty.flush
, и это нужно читать из Stderr трубы , если подпроцесс производит много выхода Stderr. В поле для комментариев недостаточно места, чтобы объяснить это ...Ответы:
У вас есть два способа сделать это: создать итератор из функций
read
илиreadline
и сделать:или
Или вы можете создать
reader
иwriter
файл. Передайтеwriter
кPopen
и прочитать изreader
Таким образом, вы получите данные, записанные
test.log
как в стандартный вывод.Единственным преимуществом файлового подхода является то, что ваш код не блокируется. Таким образом, вы можете делать все, что хотите, в то же время и читать, когда вы хотите от
reader
неблокирующим способом. Когда вы используетеPIPE
,read
иreadline
функции будут блокироваться до тех пор, пока один канал не будет записан в канал, или строка не будет записана в канал соответственно.источник
iter(process.stdout.readline, b'')
(т. Е. Часовой, передаваемый iter, должен быть двоичной строкой, посколькуb'' != ''
.for line in iter(process.stdout.readline, b''): sys.stdout.buffer.write(line)
process = subprocess.Popen(command, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) for line in iter(process.stdout.readline, b'') sys.stdout.write(line.decode(sys.stdout.encoding))
Резюме (или версия "tl; dr"): легко, когда есть не более одного
subprocess.PIPE
, иначе сложно.Может быть, пришло время объяснить немного о том, как
subprocess.Popen
работает.(Предостережение: это для Python 2.x, хотя 3.x похож; и я довольно размышляю над вариантом Windows. Я понимаю, что такое POSIX гораздо лучше.)
Popen
Функция должна иметь дело с нулевой до трех ввода / вывода потоков, несколько одновременно. Они обозначеныstdin
,stdout
и ,stderr
как обычно.Вы можете предоставить:
None
, указывая, что вы не хотите перенаправлять поток. Вместо этого он унаследует их как обычно. Обратите внимание, что в системах POSIX, по крайней мере, это не означает, что он будет использовать Pythonsys.stdout
, просто фактический вывод Python ; см. демонстрацию в конце.int
Значение. Это «сырой» файловый дескриптор (по крайней мере, в POSIX). (Примечание:PIPE
иSTDOUT
на самом деле они хранятсяint
внутри, но являются «невозможными» дескрипторами, -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
-int
или потокfileno()
- все еще легко, так как ОС делает всю работу. Python просто должен раскрутить подпроцесс, соединив его stdin, stdout и / или stderr с предоставленными файловыми дескрипторами.Все еще легкий случай: одна труба
Если вы перенаправите только один поток,
Pipe
все еще будет довольно просто. Давайте выберем один поток за раз и посмотрим.Предположим, что вы хотите предоставить некоторые из них
stdin
, но отпуститеstdout
иstderr
не будете перенаправлены, или перейдите к дескриптору файла. Как родительский процесс, ваша программа на Python просто нуждаетсяwrite()
в передаче данных по конвейеру. Вы можете сделать это самостоятельно, например:или вы можете передать данные stdin
proc.communicate()
, что затем и делает, какstdin.write
показано выше. Нет возвращаемого результата, поэтомуcommunicate()
есть только одна реальная работа: он также закрывает канал для вас. (Если вы не звоните,proc.communicate()
вы должны позвонить,proc.stdin.close()
чтобы закрыть канал, чтобы подпроцесс знал, что данные больше не поступают.)Предположим, вы хотите захватить,
stdout
но оставитьstdin
и вstderr
покое. Опять же, это просто: просто позвонитеproc.stdout.read()
(или эквивалент), пока не закончится вывод. Такproc.stdout()
как это обычный поток ввода / вывода Python, вы можете использовать все обычные конструкции, например:или, опять же, вы можете использовать
proc.communicate()
, который просто делаетread()
для вас.Если вы хотите захватить только
stderr
, он работает так же, как сstdout
.Есть еще один трюк, прежде чем все станет сложно. Предположим, вы хотите захватить
stdout
, а также захватить,stderr
но на том же канале, что и стандартный вывод:В этом случае
subprocess
«читы»! Что ж, он должен это сделать, так что это на самом деле не обман: он запускает подпроцесс как со своим stdout, так и со своим stderr, направленным в (единственный) дескриптор канала, который возвращается обратно в свой родительский (Python) процесс. На родительской стороне снова есть только один дескриптор канала для чтения вывода. Весь вывод "stderr" отображается вproc.stdout
, и если вы вызываетеproc.communicate()
, результат stderr (второе значение в кортеже) будетNone
, а не строка.Трудные случаи: две или более труб
Все проблемы возникают, когда вы хотите использовать как минимум две трубы. На самом деле, сам
subprocess
код имеет этот бит:Но, увы, здесь мы сделали, по крайней мере, две, а может и три, разные трубы, так что
count(None)
возвращает либо 1, либо 0. Мы должны делать что-то сложное.В Windows это используется
threading.Thread
для накопления результатов дляself.stdout
иself.stderr
, и родительский поток доставляетself.stdin
входные данные (а затем закрывает канал).В POSIX это используется,
poll
если доступно, в противном случаеselect
, чтобы накапливать выходные данные и доставлять ввод стандартного ввода. Все это выполняется в (единственном) родительском процессе / потоке.Здесь нужны темы или опрос / выборка, чтобы избежать тупика. Предположим, например, что мы перенаправили все три потока на три отдельных канала. Предположим далее, что существует небольшое ограничение на то, сколько данных можно вставить в канал, прежде чем процесс записи будет приостановлен, ожидая, пока процесс чтения «очистит» канал от другого конца. Давайте установим это небольшое ограничение в один байт, просто для иллюстрации. (Это на самом деле, как все работает, за исключением того, что предел намного больше, чем один байт.)
Если родительский (Python) процесс пытается записать несколько байтов, скажем,
'go\n'
вproc.stdin
, первый байт входит, а затем второй вызывает приостановку процесса Python, ожидая, пока подпроцесс прочитает первый байт, опустошив канал.Между тем, предположим, что подпроцесс решает напечатать дружественное «Привет! Не паникуйте!» приветствие. The
H
отправляется в свой канал stdout, ноe
вызывает его приостановку, ожидая, когда его родитель прочитает этоH
, освобождая канал stdout.Теперь мы застряли: процесс Python спит, ожидая, чтобы закончить, говоря «go», и подпроцесс также спит, ожидая, чтобы закончить, говоря «Hello! Don't Panic!».
subprocess.Popen
Код устраняет эту проблему с резьбонарезным или -выбором / опросом. Когда байты могут пройти по каналам, они идут. Когда они не могут, только поток (не весь процесс) должен спать - или, в случае выбора / опроса, процесс Python ожидает одновременно "может записать" или "данные доступны", записывает в stdin процесса только когда есть место, и читает свои stdout и / или stderr только тогда, когда данные готовы.proc.communicate()
Код ( на самом деле ,_communicate
где волосатые случаи обрабатываются) возвращает сразу все данные стандартного устройства ввода (если таковые имеются) были посланы и все данные STDOUT и / или STDERR накоплено.Если вы хотите читать как по двум, так
stdout
иstderr
по двум разным каналам (независимо отstdin
перенаправления), вам также нужно избегать тупиковых ситуаций. Сценарий взаимоблокировки здесь другой - он возникает, когда подпроцесс записывает что-то долгоеstderr
время, пока вы извлекаете данныеstdout
, или наоборот, - но он все еще там.Демо
Я обещал продемонстрировать, что не перенаправленные Python
subprocess
пишут в стандартный вывод, а неsys.stdout
. Итак, вот код:Когда запустить:
Обратите внимание, что первая подпрограмма потерпит неудачу, если вы добавите
stdout=sys.stdout
, так какStringIO
объект не имеетfileno
. Второй пропустит,hello
если вы добавите,stdout=sys.stdout
такsys.stdout
как был перенаправлен наos.devnull
.(Если вы перенаправите Python file-descriptor-1, подпроцесс будет следовать за этим перенаправлением. При
open(os.devnull, 'w')
вызове создается поток, значение которогоfileno()
больше 2.)источник
sys.stdout
) идет в стандартный вывод Python , а не в стандартный вывод программы (sys.
) Python . Что я признаю, это ... странное различие. Есть ли лучший способ выразить это?asyncio
основанный на коде код, который реализует «жесткую часть» (одновременно обрабатывает несколько каналов) переносимым способом . Вы можете сравнить его с кодом, который использует несколько потоков (teed_call()
), чтобы сделать то же самое .Мы также можем использовать файловый итератор по умолчанию для чтения stdout вместо использования конструкции iter с readline ().
источник
Если вы можете использовать сторонние библиотеки, вы можете использовать что-то вроде
sarge
(раскрытие: я его сопровождающий). Эта библиотека позволяет неблокировать доступ к выходным потокам из подпроцессов - она наложена наsubprocess
модуль.источник
Решение 1: Журнал
stdout
Иstderr
одновременно в реальном времениПростое решение, которое записывает как stdout, так и stderr одновременно, построчно в реальном времени в файл журнала.
Решение 2. Функция
read_popen_pipes()
, позволяющая выполнять итерации по обоим каналам (stdout / stderr) одновременно в реальном времениисточник
Хорошим, но «тяжеловесным» решением является использование Twisted - см. Дно.
Если вы готовы жить только с чем-то вроде stdout, это должно сработать:
(Если вы используете read (), он пытается прочитать весь «файл», который бесполезен, то, что мы действительно могли бы использовать здесь, это то, что читает все данные, которые сейчас находятся в канале)
Можно также попытаться приблизиться к этому с помощью потоков, например:
Теперь мы могли бы также добавить stderr, имея два потока.
Однако обратите внимание, что документы подпроцесса не рекомендуют использовать эти файлы напрямую и рекомендуют их использовать
communicate()
(в основном это касается взаимоблокировок, которые, как я думаю, не является проблемой выше), а решения немного хитрые, так что на самом деле кажется, что модуль подпроцесса не совсем подходит работа (также см .: http://www.python.org/dev/peps/pep-3145/ ), и нам нужно взглянуть на что-то еще.Более сложным решением является использование Twisted, как показано здесь: https://twistedmatrix.com/documents/11.1.0/core/howto/process.html
То, как вы делаете это с помощью Twisted, - это создание вашего процесса с использованием
reactor.spawnprocess()
и предоставлением,ProcessProtocol
который затем обрабатывает вывод асинхронно. Пример кода Python для Twisted находится здесь: https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.pyисточник
read()
вызов до тех пор, пока не завершится подпроцесс, а родительский процесс не получитEOF
канал.В дополнение ко всем этим ответам, один простой подход также может быть следующим:
Циклически читайте поток до тех пор, пока он читается, и если он получает пустой результат, остановите его.
Ключевым моментом здесь является то, что
readline()
возвращает строку (с\n
в конце), пока есть выходной и пустой, если это действительно в конце.Надеюсь, это кому-нибудь поможет.
источник
Исходя из всего вышесказанного, я предлагаю слегка модифицированную версию (python3):
None
Код:
источник
returncode
Часть имеет решающее значение в моем случае.Похоже, что выход с буферизацией строки будет работать для вас, в этом случае может подойти что-то вроде следующего. (Предостережение: это не проверено.) Это даст только стандартный вывод подпроцесса в режиме реального времени. Если вы хотите использовать как stderr, так и stdout в режиме реального времени, вам придется сделать что-то более сложное
select
.источник
Почему бы не установить
stdout
прямо наsys.stdout
? И если вам нужно также вывести в журнал, вы можете просто переопределить метод записи f.источник
stdout
дескриптор файла на дескриптор файла переданного объекта файла. Метод write никогда не будет вызван (по крайней мере, это то, что делает подпроцесс для stderr, я думаю, что это то же самое для stdout).Все вышеперечисленные решения, которые я попробовал, не смогли разделить вывод stderr и stdout (несколько каналов) или были заблокированы навсегда, когда буфер канала ОС был заполнен, что происходит, когда команда, которую вы запускаете, выводит слишком быстро (для Python есть предупреждение об этом) опрос () руководство подпроцесса). Единственный надежный способ, который я нашел, был через select, но это решение только для posix:
источник
Аналогично предыдущим ответам, но следующее решение работало для меня на окнах, использующих Python3, чтобы обеспечить общий метод для печати и входа в систему в реальном времени ( getting-realtime-output-using-python ):
источник
Я думаю, что
subprocess.communicate
метод немного вводит в заблуждение: он фактически заполняет stdout и stderr, которые вы указали вsubprocess.Popen
.Тем не менее, чтение из
subprocess.PIPE
что вы можете предоставить вsubprocess.Popen
«s STDOUT и STDERR параметры в конечном итоге заполнить буфера операционной системы труб и тупиковым приложение (особенно , если у Вас есть несколько процессов / потоков , которые должны использоватьsubprocess
).Мое предлагаемое решение состоит в том, чтобы предоставить stdout и stderr файлами - и читать содержимое файлов вместо чтения из взаимоблокировки
PIPE
. Эти файлы могут бытьtempfile.NamedTemporaryFile()
- к которым также можно получить доступ для чтения, пока они записываютсяsubprocess.communicate
.Ниже приведен пример использования:
И это исходный код, который готов к использованию с как можно большим количеством комментариев, чтобы объяснить, что он делает:
Если вы используете Python 2, убедитесь, что сначала установили последнюю версию пакета subprocess32 из pypi.
источник
Вот класс, который я использую в одном из моих проектов. Он перенаправляет вывод подпроцесса в журнал. Сначала я попытался просто переписать метод write, но это не работает, поскольку подпроцесс никогда не вызовет его (перенаправление происходит на уровне файлового дескриптора). Поэтому я использую свой собственный канал, аналогичный тому, как это делается в подпроцесс-модуле. Преимущество заключается в инкапсуляции всей логики логирования / печати в адаптере, и вы можете просто передать экземпляры регистратора в
Popen
:subprocess.Popen("/path/to/binary", stderr = LogAdapter("foo"))
Если вам не нужна регистрация, но вы просто хотите использовать ее,
print()
вы, очевидно, можете удалить большие части кода и сократить класс. Кроме того, можно расширить его__enter__
и__exit__
метод и вызватьfinished
в__exit__
таким образом , чтобы вы могли легко использовать его в качестве контекста.источник
Ни одно из решений Pythonic не сработало для меня. Оказалось, что
proc.stdout.read()
или подобное может заблокировать навсегда.Поэтому я использую
tee
так:Это решение удобно, если вы уже используете
shell=True
.${PIPESTATUS}
фиксирует статус успеха всей цепочки команд (доступно только в Bash). Если я опущу&& exit ${PIPESTATUS}
, то это всегда будет возвращать ноль, так какtee
никогда не терпит неудачу.unbuffer
может понадобиться для печати каждой строки непосредственно в терминал, вместо того, чтобы ждать слишком долго, пока «буферный канал» не будет заполнен. Тем не менее, unbuffer проглатывает состояние выхода assert (SIG Abort) ...2>&1
также записывает stderror в файл.источник