Как я могу печатать и отображать вывод подпроцесса и вывод stderr без искажений? - PullRequest
8 голосов
/ 11 октября 2011

Может быть, есть кто-то в эфире, который может помочь мне с этим.(Я видел несколько подобных вопросов в 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, взгляните на еще один из моих вопросов о учете накладных расходов подпроцесса во времени .

Ответы [ 3 ]

10 голосов
/ 11 октября 2011

Сделайте неблокирование каналов с помощью 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 для чтения данных
0 голосов
/ 21 июня 2014

Когда я проверял это, казалось, что 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()

Однако я не уверен, что это потокобезопасно.

0 голосов
/ 22 мая 2014

Объединяя этот ответ с этим , у меня работает следующий код:

import subprocess, sys
p = subprocess.Popen(args, stderr=sys.stdout.fileno(), stdout=subprocess.PIPE)
for line in iter(p.stdout.readline, ""):
 print line,
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...