Как я могу печатать и отображать вывод подпроцесса и вывод stderr без искажений?
Может быть, есть кто-то в эфире, который может помочь мне с этим. (Я видел ряд подобных вопросов в SO, но ни один из них не касается как стандартной ошибки, так и стандартной ошибки, или не имеет отношения к ситуации, совершенно похожей на мою, отсюда и новый вопрос.)
У меня есть функция Python, которая открывает подпроцесс, ждет его завершения, затем выводит код возврата, а также содержимое стандартного выхода и стандартных каналов ошибок. Пока процесс запущен, я хотел бы также отображать вывод обоих каналов по мере их заполнения. Моя первая попытка привела к чему-то вроде этого:
process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = str()
stderr = str()
returnCode = None
while True:
# collect return code and pipe info
stdoutPiece = process.stdout.read()
stdout = stdout + stdoutPiece
stderrPiece = process.stderr.read()
stderr = stderr + stderrPiece
returnCode = process.poll()
# check for the end of pipes and return code
if stdoutPiece == '' and stderrPiece == '' and returnCode != None:
return returnCode, stdout, stderr
if stdoutPiece != '': print(stdoutPiece)
if stderrPiece != '': print(stderrPiece)
Хотя есть пара проблем с этим. Так как read()
читает до EOF
, первая строка while
цикл не вернется, пока подпроцесс не закроет канал.
Я мог бы заменить read()
в пользу read(int)
но напечатанный вывод искажается, обрезается в конце прочитанных символов. Я мог бы readline()
в качестве замены, но напечатанный вывод искажается чередующимися строками вывода и ошибками, когда одновременно происходит много и того, и другого.
Возможно, есть read-until-end-of-buffer()
вариант, о котором я не знаю? Или, может быть, это можно реализовать?
Может быть, лучше всего реализовать sys.stdout
обертка, как предлагается в этом ответе на другой пост? Однако я хотел бы использовать только оболочку в этой функции.
Есть другие идеи от сообщества?
Я ценю помощь!:)
РЕДАКТИРОВАТЬ: Решение действительно должно быть кроссплатформенным, но если у вас есть идеи, которых нет, пожалуйста, поделитесь ими, чтобы продолжить мозговой штурм.
Для еще одного из моих царапин на головке подпроцесса Python, взгляните на еще один из моих вопросов об учете затрат времени на подпроцесс.
3 ответа
Сделайте трубы неблокирующими, используя fcntl.fcntl
и использовать select.select
подождать, пока данные станут доступны в любом канале. Например:
# Helper function to add the O_NONBLOCK flag to a file descriptor
def make_async(fd):
fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
# Helper function to read some data from a file descriptor, ignoring EAGAIN errors
def read_async(fd):
try:
return fd.read()
except IOError, e:
if e.errno != errno.EAGAIN:
raise e
else:
return ''
process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
make_async(process.stdout)
make_async(process.stderr)
stdout = str()
stderr = str()
returnCode = None
while True:
# Wait for data to become available
select.select([process.stdout, process.stderr], [], [])
# Try reading some data from each
stdoutPiece = read_async(process.stdout)
stderrPiece = read_async(process.stderr)
if stdoutPiece:
print stdoutPiece,
if stderrPiece:
print stderrPiece,
stdout += stdoutPiece
stderr += stderrPiece
returnCode = process.poll()
if returnCode != None:
return (returnCode, stdout, stderr)
Обратите внимание, что fcntl
доступно только на Unix-подобных платформах, включая Cygwin.
Если вам нужно, чтобы он работал на Windows без Cygwin, это выполнимо, но гораздо сложнее. Вам придется:
- Используйте библиотеку pywin32 для вызова нативного Win32 API
- использование
SetNamedPipeHandleState
сPIPE_NOWAIT
сделать трубы stdout и stderr неблокирующими - использование
WaitForMultipleObjects
вместоselect
ждать, пока данные станут доступными - использование
ReadFile
читать данные
Объединяя этот ответ с этим, у меня работает следующий код:
import subprocess, sys
p = subprocess.Popen(args, stderr=sys.stdout.fileno(), stdout=subprocess.PIPE)
for line in iter(p.stdout.readline, ""):
print line,
Когда я проверял это, казалось, что readline() блокирует. Однако я смог получить доступ к stdout и stderr отдельно, используя потоки. Пример кода выглядит следующим образом:
import os
import sys
import subprocess
import threading
class printstd(threading.Thread):
def __init__(self, std, printstring):
threading.Thread.__init__(self)
self.std = std
self.printstring = printstring
def run(self):
while True:
line = self.std.readline()
if line != '':
print self.printstring, line.rstrip()
else:
break
pythonfile = os.path.join(os.getcwd(), 'mypythonfile.py')
process = subprocess.Popen([sys.executable,'-u',pythonfile], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print 'Process ID:', process.pid
thread1 = printstd(process.stdout, 'stdout:')
thread2 = printstd(process.stderr, 'stderr:')
thread1.start()
thread2.start()
threads = []
threads.append(thread1)
threads.append(thread2)
for t in threads:
t.join()
Однако я не уверен, что это потокобезопасно.