Запустите команду и получите ее stdout, stderr отдельно в режиме реального времени, как в терминале
были предоставлены два ответа, один из которых касается первых двух критериев и будет хорошо работать, когда вам просто нужны как stdout, так и stderr с использованием потоков и очереди. Другой ответ использует select, неблокирующий метод для чтения файловых дескрипторов, и pty, метод "обмануть" порожденный процесс, полагая, что он работает в реальном терминале, как если бы он был запущен из Bash напрямую, но может или не может иметь побочных эффектов. Я хотел бы принять оба ответа, потому что "правильный" метод действительно зависит от ситуации и от того, почему вы обрабатываете в первую очередь, но, увы, я мог принять только один.
Я пытаюсь найти способ в Python, чтобы запускать другие программы таким образом, что:
- stdout и stderr запускаемой программы могут быть зарегистрированы отдельно.
- stdout и stderr запускаемой программы могут быть просматривается в почти реальном времени, так что если дочерний процесс зависает, пользователь может видеть. (т. е. мы не ждем завершения исполнения печать stdout / stderr пользователю)
- бонусные критерии: запускаемая программа не знает, что она запускается через python, и, таким образом не будет делать неожиданных вещей (например, chunk его выход вместо печатать его в реальное временя, или выход потому что он требует стержня к просмотр его вывода). Этот маленький критерий в значительной степени означает, что нам понадобится чтобы использовать 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 является pure-python и может быть с
sudo pip установить pexpect
Я думаю, что решение будет включать модуль Pty python , который является чем-то вроде черного искусства, которое я не могу найти никого, кто знает, как использовать. Возможно так знает :) В качестве хедз-апа я рекомендую вам использовать ' curl www.google.com-в качестве тестовой команды, потому что она почему-то выводит свой статус на stderr :D
обновление: Хорошо, поэтому библиотека pty не подходит для потребления человеком. Документы, по сути, являются исходным кодом. Любое представленное решение, которое блокирует, а не асинхронно, не будет работать здесь. Метод Threads / Queue от Padraic Cunningham отлично работает, хотя добавление поддержки pty невозможно - и это "грязно" (процитировать #python Freenode). Кажется, это единственное решение, подходящее для производственный стандартный код использует Twisted framework, который даже поддерживает pty как логический переключатель для запуска процессов точно так же, как если бы они были вызваны из оболочки. Но добавление Twisted в проект требует полной перезаписи всего кода. Это полный облом :/
3 ответов
stdout и stderr запускаемой программы могут регистрироваться отдельно.
вы не можете использовать pexpect
потому что оба stdout и stderr идут к тому же pty
и после этого их невозможно разделить.
stdout и stderr запускаемой программы можно просматривать в почти реальном времени, так что если дочерний процесс зависает, пользователь может видеть. (т. е. мы не ждем выполнения завершения печати stdout / stderr пользователю)
если выход подпроцесса не является tty, то вероятно, что он использует буферизацию блоков и поэтому, если он не производит много выхода, то это не будет "реальное время" например, если буфер равен 4K, то ваш родительский процесс Python ничего не увидит, пока дочерний процесс не напечатает символы 4K и буфер не переполнится или не будет сброшен явно (внутри подпроцесса). Этот буфер находится внутри дочернего процесса и нет стандартных способов управлять им извне. Вот изображение, которое показывает буферы stdio и буфер канала для command 1 | command2
shell pipeline:
запускаемая программа не знает, что она запускается через python, и, таким образом, не будет делать неожиданных вещей (например, chunk его вывод вместо печати в режиме реального времени или выхода, потому что он требует терминала для просмотра его вывода).
кажется, вы имели в виду скорее всего, ваш дочерний процесс блокирует свой вывод, а не промывает каждую выходную строку как можно скорее, если вывод перенаправляется в канал (когда вы используете stdout=PIPE
в Python). Это означает, что по умолчанию резьбонарезной или asyncio solutions не будет работать, как в вашем случае.
есть несколько вариантов, чтобы обойти его:
команда может принять аргумент командной строки, например
grep --line-buffered
илиpython -u
, чтобы отключить буферизацию блоков.-
stdbuf
работает для некоторых программ то есть, вы можете запустить['stdbuf', '-oL', '-eL'] + command
используя решение threading или 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 на стандартный вывод, стандартный вывод. Вы можете попробовать, достаточно ли одного pty в вашем случае, например, setstderr=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)
в то время как ответ Дж.Ф. Себастьяна, безусловно, решает суть проблемы, я запускаю python 2.7 (который не был в исходных критериях), поэтому я просто бросаю это всем другим усталым путешественникам, которые просто хотят вырезать/вставить какой-то код. Я еще не тестировал это, но по всем командам, которые я пробовал, он работает отлично :) возможно, ты захочешь измениться .декодировать ('ascii') до .decode ('utf-8') - im все еще тестирует этот бит.
#!/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