Запустите команду и получите ее stdout, stderr отдельно в режиме реального времени, как в терминале
Было предоставлено два ответа, один из которых касается первых двух критериев и будет хорошо работать, когда вам просто нужны и stdout, и stderr, используя Threads и Queue. Другой ответ использует select, неблокирующий метод для чтения файловых дескрипторов, и pty, метод, чтобы "обмануть" порожденный процесс, заставив его поверить, что он работает в реальном терминале так же, как если бы он запускался непосредственно из Bash - но может или может не иметь побочных эффектов. Я хотел бы принять оба ответа, потому что "правильный" метод действительно зависит от ситуации и того, почему вы в первую очередь выполняете подпроцесс, но, увы, я мог принять только один.
Я пытаюсь найти способ в Python для запуска других программ таким образом, что:
- Stdout и stderr запускаемой программы могут быть зарегистрированы отдельно.
- Stdout и stderr запускаемой программы можно просматривать почти в реальном времени, так что, если дочерний процесс зависает, пользователь может видеть. (т.е. мы не ждем завершения выполнения, прежде чем печатать stdout / stderr для пользователя)
- Бонусные критерии: запускаемая программа не знает, что она запускается через python, и, следовательно, не будет делать неожиданные вещи (например, чанкировать ее вывод вместо печати в реальном времени или выходить, потому что она требует, чтобы терминал просматривал ее вывод), Этот небольшой критерий в значительной степени означает, что нам нужно использовать pty, я думаю.
Вот то, что я получил до сих пор... Метод 1:
def method1(command):
## subprocess.communicate() will give us the stdout and stderr sepurately,
## but we will have to wait until the end of command execution to print anything.
## This means if the child process hangs, we will never know....
proc=subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, executable='/bin/bash')
stdout, stderr = proc.communicate() # record both, but no way to print stdout/stderr in real-time
print ' ######### REAL-TIME ######### '
######## Not Possible
print ' ########## RESULTS ########## '
print 'STDOUT:'
print stdout
print 'STDOUT:'
print stderr
Способ 2
def method2(command):
## Using pexpect to run our command in a pty, we can see the child's stdout in real-time,
## however we cannot see the stderr from "curl google.com", presumably because it is not connected to a pty?
## Furthermore, I do not know how to log it beyond writing out to a file (p.logfile). I need the stdout and stderr
## as strings, not files on disk! On the upside, pexpect would give alot of extra functionality (if it worked!)
proc = pexpect.spawn('/bin/bash', ['-c', command])
print ' ######### REAL-TIME ######### '
proc.interact()
print ' ########## RESULTS ########## '
######## Not Possible
Способ 3:
def method3(command):
## This method is very much like method1, and would work exactly as desired
## if only proc.xxx.read(1) wouldn't block waiting for something. Which it does. So this is useless.
proc=subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, executable='/bin/bash')
print ' ######### REAL-TIME ######### '
out,err,outbuf,errbuf = '','','',''
firstToSpeak = None
while proc.poll() == None:
stdout = proc.stdout.read(1) # blocks
stderr = proc.stderr.read(1) # also blocks
if firstToSpeak == None:
if stdout != '': firstToSpeak = 'stdout'; outbuf,errbuf = stdout,stderr
elif stderr != '': firstToSpeak = 'stderr'; outbuf,errbuf = stdout,stderr
else:
if (stdout != '') or (stderr != ''): outbuf += stdout; errbuf += stderr
else:
out += outbuf; err += errbuf;
if firstToSpeak == 'stdout': sys.stdout.write(outbuf+errbuf);sys.stdout.flush()
else: sys.stdout.write(errbuf+outbuf);sys.stdout.flush()
firstToSpeak = None
print ''
print ' ########## RESULTS ########## '
print 'STDOUT:'
print out
print 'STDERR:'
print err
Чтобы попробовать эти методы, вам нужно import sys,subprocess,pexpect
pexpect чисто python и может иметься с
sudo pip install pexpect
Я думаю, что решение будет включать pty модуль Python - что-то вроде черной магии, которую я не могу найти ни у кого, кто знает, как использовать. Возможно, SO знает:) В качестве хедз-апа я рекомендую использовать 'curl www.google.com' в качестве команды тестирования, потому что по какой-то причине она выводит свой статус на stderr:D
ОБНОВЛЕНИЕ: ОК, поэтому библиотека pty не подходит для потребления человеком. Документы, по сути, являются исходным кодом. Любое представленное решение, которое является блокирующим и не асинхронным, здесь не будет работать. Метод Threads/Queue от Padraic Cunningham прекрасно работает, хотя добавить поддержку pty невозможно - и он "грязный" (цитируя #python Фринода). Кажется, что единственное решение, подходящее для производственного стандартного кода, - это использование Twisted Framework, который даже поддерживает pty в качестве логического переключателя для запуска процессов точно так же, как если бы они были вызваны из оболочки. Но добавление Twisted в проект требует полного переписывания всего кода. Это полный облом:/
3 ответа
Stdout и stderr запускаемой программы могут быть зарегистрированы отдельно.
Вы не можете использовать pexpect
потому что и stdout и stderr идут в одно и то же pty
и после этого невозможно их разделить.
Stdout и stderr запускаемой программы можно просматривать почти в реальном времени, так что, если дочерний процесс зависает, пользователь может видеть. (т.е. мы не ждем завершения выполнения, прежде чем печатать stdout / stderr для пользователя)
Если вывод подпроцесса не является tty, то, скорее всего, он использует блочную буферизацию и, следовательно, если он не выдает много выходных данных, он не будет "в реальном времени", например, если буфер 4K, то ваш родитель Процесс Python ничего не увидит, пока дочерний процесс не напечатает 4K-символы и буфер не переполнится или не будет сброшен явно (внутри подпроцесса). Этот буфер находится внутри дочернего процесса, и нет стандартных способов управлять им извне. Вот изображение, которое показывает буферы stdio и буфер канала для command 1 | command2
Оболочка трубопровода:
Выполняемая программа не знает, что она запускается через python, и, следовательно, не будет делать неожиданные вещи (например, чанкировать ее вывод вместо печати в реальном времени или завершаться, потому что она требует, чтобы терминал просматривал ее вывод).
Кажется, вы имели в виду противоположное, т. Е. Вероятно, что ваш дочерний процесс разбивает свой вывод на части вместо того, чтобы как можно быстрее очищать каждую строку вывода, если вывод перенаправляется в канал (когда вы используете stdout=PIPE
в Python). Это означает, что используемые по умолчанию потоки или асинхронные решения не будут работать, как в вашем случае.
Есть несколько вариантов, чтобы обойти это:
команда может принять аргумент командной строки, такой как
grep --line-buffered
или жеpython -u
, чтобы отключить блок буферизации.stdbuf
работает для некоторых программ, т. е. вы можете запустить['stdbuf', '-oL', '-eL'] + command
используя приведенное выше решение для работы с потоками или asyncio, вы должны получить stdout, stderr отдельно, а строки должны появиться почти в реальном времени:#!/usr/bin/env python3 import os import sys from select import select from subprocess import Popen, PIPE with Popen(['stdbuf', '-oL', '-e0', 'curl', 'www.google.com'], stdout=PIPE, stderr=PIPE) as p: readable = { p.stdout.fileno(): sys.stdout.buffer, # log separately p.stderr.fileno(): sys.stderr.buffer, } while readable: for fd in select(readable, [], [])[0]: data = os.read(fd, 1024) # read available if not data: # EOF del readable[fd] else: readable[fd].write(data) readable[fd].flush()
наконец, вы можете попробовать
pty
+select
решение с двумяpty
s:#!/usr/bin/env python3 import errno import os import pty import sys from select import select from subprocess import Popen masters, slaves = zip(pty.openpty(), pty.openpty()) with Popen([sys.executable, '-c', r'''import sys, time print('stdout', 1) # no explicit flush time.sleep(.5) print('stderr', 2, file=sys.stderr) time.sleep(.5) print('stdout', 3) time.sleep(.5) print('stderr', 4, file=sys.stderr) '''], stdin=slaves[0], stdout=slaves[0], stderr=slaves[1]): for fd in slaves: os.close(fd) # no input readable = { masters[0]: sys.stdout.buffer, # log separately masters[1]: sys.stderr.buffer, } while readable: for fd in select(readable, [], [])[0]: try: data = os.read(fd, 1024) # read available except OSError as e: if e.errno != errno.EIO: raise #XXX cleanup del readable[fd] # EIO means EOF on some systems else: if not data: # EOF del readable[fd] else: readable[fd].write(data) readable[fd].flush() for fd in masters: os.close(fd)
Я не знаю, каковы побочные эффекты использования различных
pty
s для stdout, stderr. Вы можете попробовать, достаточно ли одного pty в вашем случае, например, установитьstderr=PIPE
и использоватьp.stderr.fileno()
вместоmasters[1]
, Комментарий вsh
источник предполагает, что есть проблемы, еслиstderr not in {STDOUT, pipe}
Если вы хотите читать из stderr и stdout и получать выходные данные отдельно, вы можете использовать поток с очередью, не слишком проверенный, но что-то вроде следующего:
import threading
import queue
def run(fd, q):
for line in iter(fd.readline, ''):
q.put(line)
q.put(None)
def create(fd):
q = queue.Queue()
t = threading.Thread(target=run, args=(fd, q))
t.daemon = True
t.start()
return q, t
process = Popen(["curl","www.google.com"], stdout=PIPE, stderr=PIPE,
universal_newlines=True)
std_q, std_out = create(process.stdout)
err_q, err_read = create(process.stderr)
while std_out.is_alive() or err_read.is_alive():
for line in iter(std_q.get, None):
print(line)
for line in iter(err_q.get, None):
print(line)
Хотя ответ JF Sebastian определенно решает суть проблемы, я использую python 2.7 (которого не было в исходных критериях), поэтому я просто добавляю его всем остальным утомленным путешественникам, которые просто хотят вырезать / вставить некоторый код. Я еще не проверил это полностью, но на всех командах, которые я пробовал, кажется, что он работает отлично:) Вы можете изменить.decode('ascii') на.decode('utf-8') - я все еще тестирую этот бит из.
#!/usr/bin/env python2.7
import errno
import os
import pty
import sys
from select import select
import subprocess
stdout = ''
stderr = ''
command = 'curl google.com ; sleep 5 ; echo "hey"'
masters, slaves = zip(pty.openpty(), pty.openpty())
p = subprocess.Popen(command, stdin=slaves[0], stdout=slaves[0], stderr=slaves[1], shell=True, executable='/bin/bash')
for fd in slaves: os.close(fd)
readable = { masters[0]: sys.stdout, masters[1]: sys.stderr }
try:
print ' ######### REAL-TIME ######### '
while readable:
for fd in select(readable, [], [])[0]:
try: data = os.read(fd, 1024)
except OSError as e:
if e.errno != errno.EIO: raise
del readable[fd]
finally:
if not data: del readable[fd]
else:
if fd == masters[0]: stdout += data.decode('ascii')
else: stderr += data.decode('ascii')
readable[fd].write(data)
readable[fd].flush()
except: pass
finally:
p.wait()
for fd in masters: os.close(fd)
print ''
print ' ########## RESULTS ########## '
print 'STDOUT:'
print stdout
print 'STDERR:'
print stderr