Подтвердить что ты не робот

Живой вывод из команды подпроцесса

Я использую python script в качестве драйвера для кода гидродинамики. Когда придет время для запуска моделирования, я использую subprocess.Popen для запуска кода, собираю вывод из stdout и stderr в subprocess.PIPE ---, тогда я могу распечатать (и сохранить в лог файл) информацию о выходе, и проверьте наличие ошибок. Проблема в том, что я понятия не имею, как продвигается код. Если я запускаю его непосредственно из командной строки, он дает мне информацию о том, на какой итерации он находится, в какое время, что следующий следующий шаг и т.д.

Есть ли способ сохранить вывод (для ведения журнала и проверки ошибок), а также создать поток в реальном времени?

Соответствующий раздел моего кода:

ret_val = subprocess.Popen( run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True )
output, errors = ret_val.communicate()
log_file.write(output)
print output
if( ret_val.returncode ):
    print "RUN failed\n\n%s\n\n" % (errors)
    success = False

if( errors ): log_file.write("\n\n%s\n\n" % errors)

Изначально я отправлял строки run_command через tee, чтобы копия попала непосредственно в файл журнала, и поток все еще выводится непосредственно на терминал, но я не могу хранить никаких ошибок (до мои знания).


Edit:

Временное решение:

ret_val = subprocess.Popen( run_command, stdout=log_file, stderr=subprocess.PIPE, shell=True )
while not ret_val.poll():
    log_file.flush()

затем в другом терминале запустите tail -f log.txt (s.t. log_file = 'log.txt').

4b9b3361

Ответ 1

У вас есть два способа сделать это: создать итератор из функций read или readline и сделать:

import subprocess
import sys
with open('test.log', 'w') as f:  # replace 'w' with 'wb' for Python 3
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for c in iter(lambda: process.stdout.read(1), ''):  # replace '' with b'' for Python 3
        sys.stdout.write(c)
        f.write(c)

или же

import subprocess
import sys
with open('test.log', 'w') as f:  # replace 'w' with 'wb' for Python 3
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for line in iter(process.stdout.readline, ''):  # replace '' with b'' for Python 3
        sys.stdout.write(line)
        f.write(line)

Или вы можете создать reader и файл writer. Передайте writer Popen и прочитайте от reader

import io
import time
import subprocess
import sys

filename = 'test.log'
with io.open(filename, 'wb') as writer, io.open(filename, 'rb', 1) as reader:
    process = subprocess.Popen(command, stdout=writer)
    while process.poll() is None:
        sys.stdout.write(reader.read())
        time.sleep(0.5)
    # Read the remaining
    sys.stdout.write(reader.read())

Таким образом, вы получите данные, записанные в test.log а также в стандартный вывод.

Единственным преимуществом файлового подхода является то, что ваш код не блокируется. Таким образом, вы можете делать все, что хотите, в то же время и читать, когда вы хотите от reader неблокирующим способом. Когда вы используете PIPE, функции read и readline будут блокироваться до тех пор, пока в канал не будет записан либо один символ, либо строка будет записана в канал соответственно.

Ответ 2

Резюме (или версия "tl; dr" ): это легко, если не более одного subprocess.PIPE, в противном случае это сложно.

Настало время немного рассказать о том, как subprocess.Popen делает свою вещь.

(Caveat: это для Python 2.x, хотя 3.x аналогичен, и я довольно нечеткий в варианте Windows. Я понимаю, что материал POSIX намного лучше.)

Функция Popen должна обрабатывать потоки ввода-вывода от нуля до трех, несколько одновременно. Они обозначаются как stdin, stdout и stderr как обычно.

Вы можете предоставить:

  • None, указывая на то, что вы не хотите перенаправлять поток. Вместо этого он наследует их как обычно. Обратите внимание, что в системах POSIX, по крайней мере, это не означает, что он будет использовать Python sys.stdout, только фактический stdout Python; см. демонстрацию в конце.
  • Значение int. Это "сырой" файловый дескриптор (по крайней мере, в POSIX). (Боковое примечание: PIPE и stdout на самом деле int внутри, но являются "невозможными" дескрипторами, -1 и -2.)
  • Поток - действительно, любой объект с методом fileno. Popen найдет дескриптор для этого потока, используя stream.fileno(), а затем продолжит действовать как значение int.
  • subprocess.PIPE, указывая, что Python должен создать канал.
  • subprocess.STDOUT (только для stderr): скажите Python использовать тот же дескриптор, что и для stdout. Это имеет смысл только в том случае, если вы указали значение None) для stdout, и даже тогда это необходимо, только если вы установите stdout=subprocess.PIPE. (В противном случае вы можете просто предоставить тот же аргумент, который вы указали для stdout, например, Popen(..., stdout=stream, stderr=stream).)

Простейшие случаи (без труб)

Если вы ничего не перенаправляете (оставьте все три в качестве значения по умолчанию None или укажите явный None), PIPE будет достаточно легко. Просто нужно открутить подпроцесс и позволить ему работать. Или, если вы перенаправляете на не-t29 > -an int или поток fileno() - все еще легко, так как ОС выполняет всю работу. Python просто должен открутить подпроцесс, подключив его stdin, stdout и/или stderr к предоставленным файловым дескрипторам.

По-прежнему простой корпус: одна трубка

Если вы перенаправляете только один поток, PIPE все еще довольно легко. Позвольте выбрать один поток за раз и посмотреть.

Предположим, вы хотите предоставить несколько stdin, но пусть stdout и stderr идут без перенаправления или переходят к файловому дескриптору. В качестве родительского процесса ваша программа Python просто должна использовать write() для отправки данных по трубе. Вы можете сделать это самостоятельно, например:

proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc

или вы можете передать данные stdin в proc.communicate(), что затем показывает stdin.write, показанное выше. Выход не возвращается, поэтому communicate() выполняет только одну другую работу: он также закрывает для вас трубку. (Если вы не вызываете proc.communicate(), вы должны вызвать proc.stdin.close(), чтобы закрыть канал, так что подпроцесс знает, что больше нет данных.)

Предположим, вы хотите захватить stdout, но оставьте stdin и stderr в покое. Опять же, это легко: просто позвоните proc.stdout.read() (или эквивалентному), пока не будет больше выхода. Поскольку proc.stdout() является обычным потоком ввода-вывода Python, вы можете использовать на нем все обычные конструкторы, например:

for line in proc.stdout:

или, опять же, вы можете использовать proc.communicate(), который просто выполняет read() для вас.

Если вы хотите записать только stderr, он будет работать так же, как с stdout.

Там еще один трюк, прежде чем все усложняется. Предположим, вы хотите захватить stdout, а также захватить stderr, но на том же самом канале, что и stdout:

proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

В этом случае subprocess "читы"! Ну, он должен это сделать, поэтому он действительно не обманывает: он запускает подпроцесс как с его stdout, так и с его stderr, направленным в (единственный) дескриптор канала, который возвращается к его родительскому (Python) процессу. На родительской стороне снова имеется только один дескриптор канала для чтения вывода. Весь вывод "stderr" отображается в proc.stdout, а если вы вызываете proc.communicate(), результат stderr (второе значение в кортеже) будет None, а не строка.

Жесткие случаи: две или более трубы

Все проблемы возникают, когда вы хотите использовать как минимум две трубы. Фактически, сам код subprocess имеет этот бит:

def communicate(self, input=None):
    ...
    # Optimization: If we are only using one pipe, or no pipe at
    # all, using select() or threads is unnecessary.
    if [self.stdin, self.stdout, self.stderr].count(None) >= 2:

Но, увы, здесь мы сделали не менее двух и, возможно, три разных канала, поэтому count(None) возвращает либо 1, либо 0. Мы должны делать что-то трудное.

В Windows это использует threading.Thread для накопления результатов для self.stdout и self.stderr, а родительский поток передает self.stdin входные данные (и затем закрывает канал).

В POSIX используется poll, если доступно, в противном случае select, чтобы накапливать выходные данные и выводить вход stdin. Все это выполняется в (одном) родительском процессе/потоке.

Для предотвращения тупика нужны темы или опрос/выбор. Предположим, например, что мы перенаправили все три потока на три отдельных канала. Предположим далее, что существует небольшое ограничение на то, сколько данных может быть набито в трубу до приостановки процесса записи, ожидая, что процесс чтения "очистит" трубу с другого конца. Пусть задано это маленькое ограничение на один байт, просто для иллюстрации. (На самом деле это работает, за исключением того, что предел намного больше, чем один байт.)

Если родительский (Python) процесс пытается записать несколько байтов - скажем, 'go\n' - proc.stdin, первый байт идет, а затем второй вызывает приостановку процесса Python, ожидая, что подпроцесс прочитает первый байт, опорожнение трубы.

Между тем, предположим, что подпроцесс решает распечатать дружественную "Hello! Do not Panic!". приветствие. H переходит в его стандартный вывод, но e заставляет его приостанавливаться, ожидая, пока его родительский текст прочитает это H, опустошая трубу stdout.

Теперь мы застряли: процесс Python спит, ожидая окончания слова "идти", а подпроцесс также спит, ожидая окончания "Hello! Do not Panic!".

Код subprocess.Popen позволяет избежать этой проблемы при использовании threading-or-select/poll. Когда байты могут проходить по трубам, они идут. Когда они не могут, только поток (а не весь процесс) должен спать - или, в случае выбора/опроса, процесс Python ждет одновременно для "can write" или "data available", записывает в процесс stdin только когда есть место, и читает его stdout и/или stderr только тогда, когда данные готовы. Код proc.communicate() (фактически _communicate, где обрабатываются волосатые случаи) возвращается после того, как все данные stdin (если есть) были отправлены и все данные stdout и/или stderr были скопированы.

Если вы хотите читать как stdout, так и stderr на двух разных каналах (независимо от перенаправления stdin), вам также необходимо избегать тупиковой ситуации. Сценарий взаимоблокировки здесь отличается: он возникает, когда подпроцесс пишет что-то долгое время stderr, пока вы извлекаете данные из stdout или наоборот, но он все еще там.


Демо

Я обещал продемонстрировать, что, не перенаправленные, Python subprocess es пишут в базовый stdout, а не sys.stdout. Итак, вот какой код:

from cStringIO import StringIO
import os
import subprocess
import sys

def show1():
    print 'start show1'
    save = sys.stdout
    sys.stdout = StringIO()
    print 'sys.stdout being buffered'
    proc = subprocess.Popen(['echo', 'hello'])
    proc.wait()
    in_stdout = sys.stdout.getvalue()
    sys.stdout = save
    print 'in buffer:', in_stdout

def show2():
    print 'start show2'
    save = sys.stdout
    sys.stdout = open(os.devnull, 'w')
    print 'after redirect sys.stdout'
    proc = subprocess.Popen(['echo', 'hello'])
    proc.wait()
    sys.stdout = save

show1()
show2()

При запуске:

$ python out.py
start show1
hello
in buffer: sys.stdout being buffered

start show2
hello

Обратите внимание, что первая процедура завершится с ошибкой, если вы добавите stdout=sys.stdout, поскольку объект StringIO не имеет fileno. Второй будет опускать hello, если вы добавите stdout=sys.stdout, поскольку sys.stdout перенаправлен на os.devnull.

(Если вы перенаправляете файл-дескриптор Python-1, подпроцесс будет следовать за этим перенаправлением. open(os.devnull, 'w') вызывает поток, чей fileno() больше 2.)

Ответ 3

Мы также можем использовать итератор файлов по умолчанию для чтения stdout вместо использования iter-конструкции с readline().

import subprocess
import sys
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for line in process.stdout:
    sys.stdout.write(line)

Ответ 4

Если вы можете использовать сторонние библиотеки, вы можете использовать что-то вроде sarge (раскрытие: я его сопровождающий). Эта библиотека позволяет неблокировать доступ к выходным потокам из подпроцессов - она распределена по модулю subprocess.

Ответ 5

Хорошим, но "тяжеловесным" решением является использование Twisted - см. снизу.

Если вы готовы жить только с тем, что должно быть в этом направлении, выполните следующие действия:

import subprocess
import sys
popenobj = subprocess.Popen(["ls", "-Rl"], stdout=subprocess.PIPE)
while not popenobj.poll():
   stdoutdata = popenobj.stdout.readline()
   if stdoutdata:
      sys.stdout.write(stdoutdata)
   else:
      break
print "Return code", popenobj.returncode

(Если вы используете read(), он пытается прочитать весь "файл", который не является полезным, то, что мы действительно могли бы использовать здесь, - это то, что сейчас читает все данные, которые в трубе)

Можно также попытаться приблизиться к этому с помощью потоковой передачи, например:

import subprocess
import sys
import threading

popenobj = subprocess.Popen("ls", stdout=subprocess.PIPE, shell=True)

def stdoutprocess(o):
   while True:
      stdoutdata = o.stdout.readline()
      if stdoutdata:
         sys.stdout.write(stdoutdata)
      else:
         break

t = threading.Thread(target=stdoutprocess, args=(popenobj,))
t.start()
popenobj.wait()
t.join()
print "Return code", popenobj.returncode

Теперь мы могли бы добавить stderr, имея два потока.

Обратите внимание, однако, что подпроцесс docs запрещает использование этих файлов напрямую и рекомендует использовать communicate() (в основном это касается тупиков, которые, по моему мнению, не являются проблемой выше), а решения - немного klunky, так что это действительно похоже на модуль подпроцесса не совсем соответствует заданию (см. также: http://www.python.org/dev/peps/pep-3145/), и нам нужно посмотреть на что-то еще.

Более привлекательным решением является использование Twisted, как показано здесь: https://twistedmatrix.com/documents/11.1.0/core/howto/process.html

Как вы это делаете с Twisted, необходимо создать свой процесс с помощью reactor.spawnprocess() и предоставить ProcessProtocol, который затем обрабатывает вывод асинхронно. Код Twisted sample Python приведен здесь: https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.py

Ответ 6

Похоже, для вас будет работать вывод с линейной буферизацией, и в этом случае может подойти что-то вроде следующего. (Предостережение: оно не проверено.) Это даст только основной процесс subprocess в режиме реального времени. Если вы хотите иметь как stderr, так и stdout в реальном времени, вам нужно сделать что-то более сложное с select.

proc = subprocess.Popen(run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
while proc.poll() is None:
    line = proc.stdout.readline()
    print line
    log_file.write(line + '\n')
# Might still be data on stdout at this point.  Grab any
# remainder.
for line in proc.stdout.read().split('\n'):
    print line
    log_file.write(line + '\n')
# Do whatever you want with proc.stderr here...

Ответ 7

Почему бы не установить stdout непосредственно на sys.stdout? И если вам нужно также выводить на журнал, вы можете просто переопределить метод записи файла f.

import sys
import subprocess

class SuperFile(open.__class__):

    def write(self, data):
        sys.stdout.write(data)
        super(SuperFile, self).write(data)

f = SuperFile("log.txt","w+")       
process = subprocess.Popen(command, stdout=f, stderr=f)

Ответ 8

Все вышеперечисленные решения, которые я попробовал, не смогли отделить вывод stderr и stdout (несколько трубок) или заблокированы навсегда, когда буфер буфера OS был заполнен, что происходит, когда команда, с которой вы работаете, выходит слишком быстро (есть предупреждение для это в руководстве python poll() подпроцесса). Единственный надежный способ, который я нашел, - это выбрать, но это решение только для posix:

import subprocess
import sys
import os
import select
# returns command exit status, stdout text, stderr text
# rtoutput: show realtime output while running
def run_script(cmd,rtoutput=0):
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    poller = select.poll()
    poller.register(p.stdout, select.POLLIN)
    poller.register(p.stderr, select.POLLIN)

    coutput=''
    cerror=''
    fdhup={}
    fdhup[p.stdout.fileno()]=0
    fdhup[p.stderr.fileno()]=0
    while sum(fdhup.values()) < len(fdhup):
        try:
            r = poller.poll(1)
        except select.error, err:
            if err.args[0] != EINTR:
                raise
            r=[]
        for fd, flags in r:
            if flags & (select.POLLIN | select.POLLPRI):
                c = os.read(fd, 1024)
                if rtoutput:
                    sys.stdout.write(c)
                    sys.stdout.flush()
                if fd == p.stderr.fileno():
                    cerror+=c
                else:
                    coutput+=c
            else:
                fdhup[fd]=1
    return p.poll(), coutput.strip(), cerror.strip()

Ответ 9

В дополнение ко всем этим ответам, один простой подход может быть следующим:

process = subprocess.Popen(your_command, stdout=subprocess.PIPE)

while process.stdout.readable():
    line = process.stdout.readline()

    if not line:
        break

    print(line.strip())

Циклически читайте поток до тех пор, пока он читается, и, если он получает пустой результат, остановите его.

Ключевым моментом здесь является то, что readline() возвращает строку (с \n в конце), пока есть вывод, и пустую, если она действительно в конце.

Надеюсь, это кому-нибудь поможет.

Ответ 10

Решение 1: одновременно регистрируйте stdout И stderr в реальном времени

Простое решение, которое одновременно регистрирует stdout и stderr в реальном времени.

import subprocess as sp
from concurrent.futures import ThreadPoolExecutor


def log_popen_pipe(p, pipe_name):

    while p.poll() is None:
        line = getattr(p, pipe_name).readline()
        log_file.write(line)


with sp.Popen(my_cmd, stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    with ThreadPoolExecutor(2) as pool:
        r1 = pool.submit(log_popen_pipe, p, 'stdout')
        r2 = pool.submit(log_popen_pipe, p, 'stderr')
        r1.result()
        r2.result()

Решение 2. Создайте итератор, который последовательно возвращает строки stdout и stderr в режиме реального времени

Функция read_popen_pipes(), позволяющая выполнять итерации по обоим каналам (stdout/stderr) одновременно в режиме реального времени:

import subprocess as sp
from queue import Queue, Empty
from concurrent.futures import ThreadPoolExecutor


def enqueue_output(file, queue):
    for line in iter(file.readline, ''):
        queue.put(line)
    file.close()


def read_popen_pipes(p):

    with ThreadPoolExecutor(2) as pool:
        q_stdout, q_stderr = Queue(), Queue()

        pool.submit(enqueue_output, p.stdout, q_stdout)
        pool.submit(enqueue_output, p.stderr, q_stderr)

        while True:

            if p.poll() is not None and q_stdout.empty() and q_stderr.empty():
                break

            out_line = err_line = ''

            try:
                out_line = q_stdout.get_nowait()
                err_line = q_stderr.get_nowait()
            except Empty:
                pass

            yield (out_line, err_line)


with sp.Popen(my_cmd, stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    for out_line, err_line in read_popen_pipes(p):
        print(out_line, end='')
        print(err_line, end='')

    return p.poll()

Ответ 11

Вот класс, который я использую в одном из моих проектов. Он перенаправляет вывод подпроцесса в журнал. Сначала я попробовал просто перезаписать метод write, но это не сработает, поскольку подпроцесс никогда не вызовет его (перенаправление происходит на уровне filedescriptor). Поэтому я использую свою собственную трубу, подобно тому, как это делается в модуле subprocess. Это имеет преимущественное значение для инкапсуляции всей логики регистрации/печати в адаптер, и вы можете просто передать экземпляры регистратора на Popen: subprocess.Popen("/path/to/binary", stderr = LogAdapter("foo"))

class LogAdapter(threading.Thread):

    def __init__(self, logname, level = logging.INFO):
        super().__init__()
        self.log = logging.getLogger(logname)
        self.readpipe, self.writepipe = os.pipe()

        logFunctions = {
            logging.DEBUG: self.log.debug,
            logging.INFO: self.log.info,
            logging.WARN: self.log.warn,
            logging.ERROR: self.log.warn,
        }

        try:
            self.logFunction = logFunctions[level]
        except KeyError:
            self.logFunction = self.log.info

    def fileno(self):
        #when fileno is called this indicates the subprocess is about to fork => start thread
        self.start()
        return self.writepipe

    def finished(self):
       """If the write-filedescriptor is not closed this thread will
       prevent the whole program from exiting. You can use this method
       to clean up after the subprocess has terminated."""
       os.close(self.writepipe)

    def run(self):
        inputFile = os.fdopen(self.readpipe)

        while True:
            line = inputFile.readline()

            if len(line) == 0:
                #no new data was added
                break

            self.logFunction(line.strip())

Если вам не нужно вести журнал, но просто хотите использовать print(), вы можете, очевидно, удалить большие части кода и сохранить класс короче. Вы также можете расширить его с помощью методов __enter__ и __exit__ и вызвать finished в __exit__, чтобы вы могли легко использовать его в качестве контекста.

Ответ 12

Аналогично предыдущим ответам, но следующее решение работало для меня на окнах, использующих Python3, для предоставления общего метода для печати и входа в систему в реальном времени (getting-realtime-output-using-python):

def print_and_log(command, logFile):
    with open(logFile, 'wb') as f:
        command = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)

        while True:
            output = command.stdout.readline()
            if not output and command.poll() is not None:
                f.close()
                break
            if output:
                f.write(output)
                print(str(output.strip(), 'utf-8'), flush=True)
        return command.poll()

Ответ 13

Исходя из всего вышесказанного, я предлагаю слегка модифицированную версию (python3):

  • цикл read вызывал readline (мне казалось, что предложенное iter-решение навсегда заблокировало меня - Python 3, Windows 7)
  • структурирован, поэтому нет необходимости дублировать обработку прочитанных данных после возврата из опроса not- None
  • stderr отправляется в stdout, поэтому оба выходных сигнала читаются
  • Добавлен код для получения значения выхода cmd.

Код:

import subprocess
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
                        stderr=subprocess.STDOUT, universal_newlines=True)
while True:
    rd = proc.stdout.readline()
    print(rd, end='')  # and whatever you want to do...
    if not rd:  # EOF
        returncode = proc.poll()
        if returncode is not None:
            break
        time.sleep(0.1)  # cmd closed stdout, but not exited yet

# You may want to check on ReturnCode here

Ответ 14

Я думаю, что метод subprocess.communicate немного вводит в заблуждение: он на самом деле заполняет stdout и stderr, которые вы указываете в subprocess.Popen.

Тем не менее, чтение из subprocess.PIPE которое вы можете предоставить subprocess.Popen Параметры stdout и stderr в конечном итоге заполнят буферы конвейера ОС и заблокируют ваше приложение (особенно если у вас несколько процессов/потоков, которые должны использовать subprocess).

Мое предлагаемое решение состоит в том, чтобы предоставить stdout и stderr файлами - и читать содержимое файлов вместо чтения из тупиковой PIPE. Эти файлы могут быть tempfile.NamedTemporaryFile() - к которым также можно получить доступ для чтения, пока они записываются в subprocess.communicate.

Ниже приведен пример использования:

        try:
            with ProcessRunner(('python', 'task.py'), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner:
                for out in process_runner:
                    print(out)
        catch ProcessError as e:
            print(e.error_message)
            raise

И это исходный код, который можно использовать с как можно большим количеством комментариев, чтобы объяснить, что он делает:

Если вы используете Python 2, убедитесь, что сначала установили последнюю версию пакета subprocess32 из pypi.


import os
import sys
import threading
import time
import tempfile
import logging

if os.name == 'posix' and sys.version_info[0] < 3:
    # Support python 2
    import subprocess32 as subprocess
else:
    # Get latest and greatest from python 3
    import subprocess

logger = logging.getLogger(__name__)


class ProcessError(Exception):
    """Base exception for errors related to running the process"""


class ProcessTimeout(ProcessError):
    """Error that will be raised when the process execution will exceed a timeout"""


class ProcessRunner(object):
    def __init__(self, args, env=None, timeout=None, bufsize=-1, seconds_to_wait=0.25, **kwargs):
        """
        Constructor facade to subprocess.Popen that receives parameters which are more specifically required for the
        Process Runner. This is a class that should be used as a context manager - and that provides an iterator
        for reading captured output from subprocess.communicate in near realtime.

        Example usage:


        try:
            with ProcessRunner(('python', task_file_path), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner:
                for out in process_runner:
                    print(out)
        catch ProcessError as e:
            print(e.error_message)
            raise

        :param args: same as subprocess.Popen
        :param env: same as subprocess.Popen
        :param timeout: same as subprocess.communicate
        :param bufsize: same as subprocess.Popen
        :param seconds_to_wait: time to wait between each readline from the temporary file
        :param kwargs: same as subprocess.Popen
        """
        self._seconds_to_wait = seconds_to_wait
        self._process_has_timed_out = False
        self._timeout = timeout
        self._process_done = False
        self._std_file_handle = tempfile.NamedTemporaryFile()
        self._process = subprocess.Popen(args, env=env, bufsize=bufsize,
                                         stdout=self._std_file_handle, stderr=self._std_file_handle, **kwargs)
        self._thread = threading.Thread(target=self._run_process)
        self._thread.daemon = True

    def __enter__(self):
        self._thread.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self._thread.join()
        self._std_file_handle.close()

    def __iter__(self):
        # read all output from stdout file that subprocess.communicate fills
        with open(self._std_file_handle.name, 'r') as stdout:
            # while process is alive, keep reading data
            while not self._process_done:
                out = stdout.readline()
                out_without_trailing_whitespaces = out.rstrip()
                if out_without_trailing_whitespaces:
                    # yield stdout data without trailing \n
                    yield out_without_trailing_whitespaces
                else:
                    # if there is nothing to read, then please wait a tiny little bit
                    time.sleep(self._seconds_to_wait)

            # this is a hack: terraform seems to write to buffer after process has finished
            out = stdout.read()
            if out:
                yield out

        if self._process_has_timed_out:
            raise ProcessTimeout('Process has timed out')

        if self._process.returncode != 0:
            raise ProcessError('Process has failed')

    def _run_process(self):
        try:
            # Start gathering information (stdout and stderr) from the opened process
            self._process.communicate(timeout=self._timeout)
            # Graceful termination of the opened process
            self._process.terminate()
        except subprocess.TimeoutExpired:
            self._process_has_timed_out = True
            # Force termination of the opened process
            self._process.kill()

        self._process_done = True

    @property
    def return_code(self):
        return self._process.returncode



Ответ 15

Я выйду на конечность и предлагаю использовать check_call. Согласно документации, он запускает операцию блокировки и вполне подходит для этой цели.

Запустить команду с аргументами. Дождитесь завершения команды. Если код возврата был равен нулю, а затем возвращает, в противном случае повысит CalledProcessError. Объект CalledProcessError будет иметь код возврата в returncode.

cmd = "some_crazy_long_script.sh"
args = {
    'shell': True,
    'cwd': if_you_need_it
}

subprocess.check_call(cmd, **args)

Ответ 16

Ни одно из решений Pythonic не сработало для меня. Оказалось, что proc.stdout.read() или аналогичный может блокировать навсегда.

Поэтому я использую tee так:

subprocess.run('./my_long_running_binary 2>&1 | tee -a my_log_file.txt && exit ${PIPESTATUS}', shell=True, check=True, executable='/bin/bash')

Это решение удобно, если вы уже используете shell=True.

${PIPESTATUS} фиксирует состояние успеха всей цепочки команд (доступно только в Bash). Если я опустил && exit ${PIPESTATUS}, то это всегда будет возвращать ноль, так как tee никогда не выходит из строя.

unbuffer может понадобиться для печати каждой строки непосредственно в терминал, вместо того, чтобы ждать слишком долго, пока "труба буфера" не будет заполнена. Тем не менее, unbuffer проглатывает состояние выхода assert (SIG Abort)...

2>&1 также записывает stderror в файл.

Ответ 17

import sys
import subprocess

f = open("log.txt","w+")
process = subprocess.Popen(command, stdout=subprocess.PIPE)
for line in iter(process.stdout.readline, ''):
    sys.stdout.write(line)
    f.write(line.replace("\n",""))
f.close()