оперативный вывод команды subprocess
я использую скрипт 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 failednn%snn" % (errors)
success = False
if( errors ): log_file.write("nn%snn" % errors)
Первоначально я трубил run_command
через tee
так что копия пошла прямо в лог-файл, а поток по-прежнему выводится непосредственно на терминал - но таким образом я не могу хранить ошибки (к моему ноуледж).
Edit:
временное решение:
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
(С. Т. log_file = 'log.txt'
).
12 ответов
у вас есть два способа сделать это либо путем создания итератора от read
или readline
функции и делать:
import subprocess
import sys
with open('test.log', 'w') as f:
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for c in iter(lambda: process.stdout.read(1), ''): # replace '' with b'' for Python 3
sys.stdout.write(c)
f.write(c)
или
import subprocess
import sys
with open('test.log', 'w') as f:
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for line in iter(process.stdout.readline, ''): # replace '' with b'' for Python 3
sys.stdout.write(line)
f.write(line)
или вы можете создать reader
и . Пройти 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
функции будут блокировать, пока один символ не будет записан в канал или строка не будет записана в канал соответственно.
резюме (или" tl;dr " версия): это легко, когда есть не более одного subprocess.PIPE
, в противном случае это трудно.
возможно, пришло время объяснить немного о том, как subprocess.Popen
делает свое дело.
(предостережение: это для Python 2.x, хотя 3.x похож; и я довольно нечеткий в варианте Windows. Я понимаю материал POSIX намного лучше.)
на Popen
функция должна иметь дело с потоками ввода-вывода от нуля до трех, несколько одновременно. Они обозначаются stdin
, stdout
и stderr
как обычно.
вы можете предоставить:
-
None
, указывая, что вы не хотите перенаправлять поток. Он унаследует их, как обычно. Обратите внимание, что в системах POSIX, по крайней мере, это не означает, что он будет использовать Pythonsys.stdout
, просто в Python фактический stdout; см. демонстрацию в конце. - An
int
значение. Это" сырой " файловый дескриптор (по крайней мере, в POSIX). (Примечание:PIPE
иSTDOUT
на самом делеint
s внутренне, но являются "невозможными" дескрипторами, -1 и -2.) - поток-действительно, любой объект с
fileno
метод.Popen
найдет дескриптор для этого потока, используяstream.fileno()
, а затем продолжайте как дляint
значение. -
subprocess.PIPE
, указывая, что Python должен создать канал. -
subprocess.STDOUT
(forstderr
только): скажите 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
go un-redirected или перейдите к файловому дескриптору. В качестве родительского процесса ваша программа 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
и . Опять же, это просто: просто позвоните proc.stdout.read()
(или эквивалент), пока не будет больше выхода. С proc.stdout()
является обычным потоком ввода-вывода Python, вы можете использовать все обычные конструкции на нем, например:
for line in proc.stdout:
или, опять же, вы можете использовать proc.communicate()
, который просто делает read()
для вас.
если вы хотите захват только stderr
, он работает так же, как с stdout
.
есть еще один трюк, прежде чем все станет трудно. Предположим, вы хотите захватить stdout
, а также stderr
но на той же трубе, что и stdout:
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
, и имеет Родительский поток deliver self.stdin
входные данные (а затем закройте канал).
на POSIX это использует poll
если доступно, в противном случае select
, чтобы накопить выпускаемая ввода-ввода. Все это выполняется в (одном) родителе процесс/поток.
потоки или опрос / выбор необходимы здесь, чтобы избежать взаимоблокировки. Предположим, например, что мы перенаправили все три потока в три отдельных канала. Предположим далее, что существует небольшое ограничение на то, сколько данных можно вставить в канал до приостановки процесса записи, ожидая, пока процесс чтения "очистит" канал с другого конца. Давайте установим это небольшое ограничение на один байт, просто для иллюстрации. (Это на самом деле как обстоят дела, за исключением того, что предел намного больше, чем один байт.)
если родительский (Python) процесс пытается написать несколько байтов-скажем, 'go\n'
to proc.stdin
, первый байт входит, а затем второй заставляет процесс Python приостанавливаться, ожидая, пока подпроцесс прочитает первый байт, опорожняя канал.
между тем, предположим, что подпроцесс решает напечатать дружественное " Привет! Не паникуй!" приветствие. The H
идет в свою трубу stdout, но e
вызывает приостановите, ожидая, пока его родитель прочитает это H
, опорожняя трубу stdout.
теперь мы застряли: процесс Python спит, ожидая, чтобы закончить говорить "go", и подпроцесс также спит, ожидая, чтобы закончить говорить " Привет! Не паникуй!".
на subprocess.Popen
код позволяет избежать этой проблемы с потоковой передачей или выбором / опросом. Когда байты могут пройти по каналам, они идут. Когда они не могут, только поток (не весь процесс) должен спать-или, в случае выбора / опроса, Процесс Python одновременно ожидает "может записывать" или "доступные данные", записывает в stdin процесса только тогда, когда есть место, и читает его stdout и/или stderr только тогда, когда данные готовы. The proc.communicate()
код (на самом деле _communicate
где обрабатываются волосатые случаи) возвращает после того, как все данные stdin (если таковые имеются) были отправлены и все данные stdout и/или stderr были накоплены.
если вы хотите прочитать и stdout
и stderr
на двух разных труб (независимо от каких-либо stdin
redirection), вам также нужно будет избегать тупика. Сценарий взаимоблокировки здесь другой-он возникает, когда подпроцесс записывает что-то длинное в stderr
пока вы вытаскиваете данные из stdout
, или наоборот-но он все еще там.
Демо
я обещал продемонстрировать, что, не перенаправленный, Python subprocess
es напишите в базовый stdout, а не 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
, как
Если вы можете использовать сторонние библиотеки, вы можете использовать что-то вроде sarge
(раскрытие информации: я его сопровождающий). Эта библиотека позволяет неблокирующий доступ к выходным потокам из подпроцессов-он наслоен на subprocess
модуль.
мы также можем использовать итератор файлов по умолчанию для чтения stdout вместо использования конструкции iter с readline ().
import subprocess
import sys
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for line in process.stdout:
sys.stdout.write(line)
хорошим, но "тяжеловесным" решением является использование Twisted-см. Дно.
если вы готовы жить только с stdout что-то в этом роде должно работать:
import subprocess
import sys
popenobj = subprocess.Popen(["ls", "-Rl"], stdout=subprocess.PIPE)
while not popenobj.poll():
stdoutdata = popenobj.stdout.readline()
if stdoutdata:
sys.stdout.write(stdoutdata)
else:
break
print "Return code", popenobj.returncode
(если вы используете read () он пытается прочитать весь "файл", который не полезен, то, что мы действительно могли бы использовать здесь, это то, что читает все данные, которые находятся в трубе прямо сейчас)
можно также попытаться подойти к этому с помощью резьбы, например:
import subprocess
import sys
import threading
popenobj = subprocess.Popen("ls", stdout=subprocess.PIPE, shell=True)
def stdoutprocess(o):
while True:
stdoutdata = o.stdout.readline()
if stdoutdata:
sys.stdout.write(stdoutdata)
else:
break
t = threading.Thread(target=stdoutprocess, args=(popenobj,))
t.start()
popenobj.wait()
t.join()
print "Return code", popenobj.returncode
теперь мы можем потенциально добавьте 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 находится здесь: https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.py
похоже, что выход с буферизацией строк будет работать для вас, и в этом случае может подойти что-то вроде следующего. (Предостережение: это непроверено.) Это даст только stdout подпроцесса в режиме реального времени. Если вы хотите иметь stderr и stdout в режиме реального времени, вам придется сделать что-то более сложное с select
.
proc = subprocess.Popen(run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
while proc.poll() is None:
line = proc.stdout.readline()
print line
log_file.write(line + '\n')
# Might still be data on stdout at this point. Grab any
# remainder.
for line in proc.stdout.read().split('\n'):
print line
log_file.write(line + '\n')
# Do whatever you want with proc.stderr here...
почему бы не установить stdout
на sys.stdout
? И если вам также нужно вывести в журнал, то вы можете просто переопределить метод записи f.
import sys
import subprocess
class SuperFile(open.__class__):
def write(self, data):
sys.stdout.write(data)
super(SuperFile, self).write(data)
f = SuperFile("log.txt","w+")
process = subprocess.Popen(command, stdout=f, stderr=f)
вот класс, который я использую в одном из моих проектов. Он перенаправляет вывод подпроцесса в журнал. Сначала я попытался просто перезаписать метод записи, но это не работает, так как подпроцесс никогда не вызовет его (перенаправление происходит на уровне filedescriptor). Поэтому я использую свою собственную трубу, аналогично тому, как это делается в подпроцессе-модуле. Это имеет преимущество инкапсуляции всей логики ведения журнала / печати в адаптере, и вы можете просто передать экземпляры регистратора в Popen
: subprocess.Popen("/path/to/binary", stderr = LogAdapter("foo"))
class LogAdapter(threading.Thread):
def __init__(self, logname, level = logging.INFO):
super().__init__()
self.log = logging.getLogger(logname)
self.readpipe, self.writepipe = os.pipe()
logFunctions = {
logging.DEBUG: self.log.debug,
logging.INFO: self.log.info,
logging.WARN: self.log.warn,
logging.ERROR: self.log.warn,
}
try:
self.logFunction = logFunctions[level]
except KeyError:
self.logFunction = self.log.info
def fileno(self):
#when fileno is called this indicates the subprocess is about to fork => start thread
self.start()
return self.writepipe
def finished(self):
"""If the write-filedescriptor is not closed this thread will
prevent the whole program from exiting. You can use this method
to clean up after the subprocess has terminated."""
os.close(self.writepipe)
def run(self):
inputFile = os.fdopen(self.readpipe)
while True:
line = inputFile.readline()
if len(line) == 0:
#no new data was added
break
self.logFunction(line.strip())
Если вам не нужно ведение журнала, но просто хотите использовать print()
вы, очевидно, можете удалить большие части кода и сохранить класс короче. Вы также можете расширить его на __enter__
и __exit__
метод и называют finished
на __exit__
чтобы вы могли легко использовать его в качестве контекста.
все вышеперечисленные решения, которые я пробовал, не смогли отделить вывод stderr и stdout (несколько каналов) или заблокированы навсегда, когда буфер канала ОС был заполнен, что происходит, когда команда, которую вы запускаете, выводит слишком быстро (есть предупреждение для этого на python poll() руководство по подпроцессу). Единственный надежный способ, который я нашел, был через select, но это решение только для posix:
import subprocess
import sys
import os
import select
# returns command exit status, stdout text, stderr text
# rtoutput: show realtime output while running
def run_script(cmd,rtoutput=0):
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
poller = select.poll()
poller.register(p.stdout, select.POLLIN)
poller.register(p.stderr, select.POLLIN)
coutput=''
cerror=''
fdhup={}
fdhup[p.stdout.fileno()]=0
fdhup[p.stderr.fileno()]=0
while sum(fdhup.values()) < len(fdhup):
try:
r = poller.poll(1)
except select.error, err:
if err.args[0] != EINTR:
raise
r=[]
for fd, flags in r:
if flags & (select.POLLIN | select.POLLPRI):
c = os.read(fd, 1024)
if rtoutput:
sys.stdout.write(c)
sys.stdout.flush()
if fd == p.stderr.fileno():
cerror+=c
else:
coutput+=c
else:
fdhup[fd]=1
return p.poll(), coutput.strip(), cerror.strip()
В дополнение ко всем этим ответам, один простой подход также может быть следующим:
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
while process.stdout.readable():
line = rstrm.readline()
if not line:
break
print(line.strip())
цикл через читаемый поток, пока он читается, и если он получает пустой результат, остановите его.
ключ здесь в том, что readline()
возвращает строку (\n
в конце), пока есть выход и пустой, если он действительно в конце.
надеюсь, это кому-то поможет.
Я выйду на конечности здесь и предположить, используя check_call
. Согласно документации, он запускает операцию блокировки и подходит для этой цели довольно хорошо.
выполнить команду с аргументами. Дождитесь завершения команды. Если код возврата был равен нулю, затем return, иначе вызовите CalledProcessError. Объект CalledProcessError будет иметь код возврата в атрибут код возврата.
cmd = "some_crazy_long_script.sh"
args = {
'shell': True,
'cwd': if_you_need_it
}
subprocess.check_call(cmd, **args)
import sys
import subprocess
f = open("log.txt","w+")
process = subprocess.Popen(command, stdout=subprocess.PIPE)
for line in iter(process.stdout.readline, ''):
sys.stdout.write(line)
f.write(line.replace("\n",""))
f.close()