Как я могу печатать и выводить вывод субпроцесса stdout и 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
, как предложено в этом ответе на другой пост? Однако я хотел бы использовать оболочку в этой функции.
Любые другие идеи сообщества?
Я ценю помощь!:)
EDIT: решение действительно должно быть кросс-платформенным, но если у вас есть идеи, которые этого не делают, пожалуйста, поделитесь ими, чтобы продолжить мозговой штурм.
Для другого одного из моих головоломок с подпроцессом python взгляните на другой из моих вопросов на в котором учтены накладные расходы подпроцесса.
Ответы
Ответ 1
Сделайте блокировку труб с помощью 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, он выполним, но это намного, намного сложнее. Вам нужно будет:
Ответ 2
Объединив этот ответ с этим, для меня работает следующий код:
import subprocess, sys
p = subprocess.Popen(args, stderr=sys.stdout.fileno(), stdout=subprocess.PIPE)
for line in iter(p.stdout.readline, ""):
print line,
Ответ 3
Когда я протестировал его, казалось, что 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()
Однако я не уверен, что это потокобезопасно.