Я пытаюсь найти способ в 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 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
Я думаю, что решение будет включать модуль Python Pty - что-то вроде черного искусства, что я не могу найти никого, кто знает, как использовать. Возможно, SO знает :) В качестве хедз-апа я рекомендую использовать 'curl www.google.com' в качестве команды тестирования, потому что по какой-то причине она выводит свой статус на stderr: D
UPDATE-1:
Хорошо, поэтому библиотека pty не подходит для потребления человеком. Документы, по сути, являются исходным кодом. Любое представленное решение, которое является блокирующим и не асинхронным, здесь не будет работать. Метод Threads/Queue от Padraic Cunningham отлично работает, хотя добавить поддержку pty невозможно - и он "грязный" (цитируя Freenode #python). Кажется, что единственное решение, подходящее для производственного стандартного кода, - это использование Twisted Framework, который даже поддерживает pty в качестве логического переключателя для запуска процессов точно так же, как если бы они были вызваны из оболочки. Но добавление Twisted в проект требует полного переписывания всего кода. Это полный облом:/
UPDATE-2:
Было предоставлено два ответа, один из которых касается первых двух критериев и будет хорошо работать, когда вам просто нужны и stdout, и stderr, используя
Threads and Queue
. Другой ответ используетselect
, неблокирующий метод для чтения файловых дескрипторов, и pty, метод, чтобы "обмануть" порожденный процесс, заставив его поверить, что он работает в реальном терминале, как если бы он был запущен непосредственно из Bash - но может или может не иметь побочных эффектов. Я хотел бы принять оба ответа, потому что "правильный" метод действительно зависит от ситуации и того, почему вы в первую очередь выполняете подпроцесс, но, увы, я мог принять только один.