Постоянно печатать вывод подпроцесса во время работы процесса - PullRequest
159 голосов
/ 11 декабря 2010

Для запуска программ из моих Python-скриптов я использую следующий метод:

def execute(command):
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    output = process.communicate()[0]
    exitCode = process.returncode

    if (exitCode == 0):
        return output
    else:
        raise ProcessException(command, exitCode, output)

Поэтому, когда я запускаю процесс, подобный Process.execute("mvn clean install"), моя программа ждет, пока процесс не завершится, и только тогда я получаю полный вывод моей программы. Это раздражает, если я запускаю процесс, который занимает некоторое время.

Могу ли я позволить моей программе записывать выходные данные процесса построчно, опрашивая выходные данные процесса до его завершения в цикле или что-то в этом роде?

** [EDIT] Извините, я не очень хорошо искал, прежде чем опубликовать этот вопрос. Потоки на самом деле ключ. Здесь нашел пример, который показывает, как это сделать: ** Подпроцесс Python. Открыть из потока

Ответы [ 12 ]

221 голосов
/ 11 декабря 2010

Вы можете использовать iter для обработки строк, как только команда выведет их: lines = iter(fd.readline, "").Вот полный пример, показывающий типичный вариант использования (спасибо @jfs за помощь):

from __future__ import print_function # Only Python 2.x
import subprocess

def execute(cmd):
    popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True)
    for stdout_line in iter(popen.stdout.readline, ""):
        yield stdout_line 
    popen.stdout.close()
    return_code = popen.wait()
    if return_code:
        raise subprocess.CalledProcessError(return_code, cmd)

# Example
for path in execute(["locate", "a"]):
    print(path, end="")
78 голосов
/ 11 декабря 2010

Хорошо, мне удалось решить это без потоков (любые предложения, почему использование потоков было бы лучше), используя фрагмент этого вопроса Перехват вывода stdout подпроцесса во время его выполнения

def execute(command):
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    # Poll process for new output until finished
    while True:
        nextline = process.stdout.readline()
        if nextline == '' and process.poll() is not None:
            break
        sys.stdout.write(nextline)
        sys.stdout.flush()

    output = process.communicate()[0]
    exitCode = process.returncode

    if (exitCode == 0):
        return output
    else:
        raise ProcessException(command, exitCode, output)
53 голосов
/ 04 февраля 2015

Для вывода выходных данных подпроцесса построчно, как только его буфер stdout будет очищен в Python 3:

from subprocess import Popen, PIPE, CalledProcessError

with Popen(cmd, stdout=PIPE, bufsize=1, universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='') # process line here

if p.returncode != 0:
    raise CalledProcessError(p.returncode, p.args)

Примечание: вам не нужно p.poll() - цикл завершается, когда eofдостигнутоИ вам не нужно iter(p.stdout.readline, '') - ошибка опережающего чтения исправлена ​​в Python 3.

См. Также, Python: чтение потокового ввода от subprocess.communicate () .

5 голосов
/ 24 декабря 2014

@ tokland

попробовал ваш код и исправил его для 3.4, а windows dir.cmd - это простая команда dir, сохраненная как cmd-файл

import subprocess
c = "dir.cmd"

def execute(command):
    popen = subprocess.Popen(command, stdout=subprocess.PIPE,bufsize=1)
    lines_iterator = iter(popen.stdout.readline, b"")
    while popen.poll() is None:
        for line in lines_iterator:
            nline = line.rstrip()
            print(nline.decode("latin"), end = "\r\n",flush =True) # yield line

execute(c)
3 голосов
/ 01 ноября 2018

Чтобы ответить на исходный вопрос, лучший способ IMO - просто перенаправить подпроцесс stdout непосредственно на stdout вашей программы (опционально, то же самое можно сделать для stderr, как в примере ниже)

p = Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
p.communicate()
3 голосов
/ 05 октября 2018

В Python> = 3.5 использование subprocess.run работает для меня:

import subprocess

cmd = 'echo foo; sleep 1; echo foo; sleep 2; echo foo'
subprocess.run(cmd, shell=True)

(получение вывода во время выполнения также работает без shell=True) https://docs.python.org/3/library/subprocess.html#subprocess.run

3 голосов
/ 21 июня 2014

Для тех, кто пытается получить ответы на этот вопрос, чтобы получить стандартный вывод из скрипта Python, обратите внимание, что Python буферизует свой стандартный вывод, и поэтому для его просмотра может потребоваться некоторое время.

Это можно исправить, добавив следующее после каждой записи stdout в целевой скрипт:

sys.stdout.flush()
2 голосов
/ 11 июля 2016

Если кто-то хочет одновременно читать потоки stdout и stderr, используя потоки, я пришел к следующему:

import threading
import subprocess
import Queue

class AsyncLineReader(threading.Thread):
    def __init__(self, fd, outputQueue):
        threading.Thread.__init__(self)

        assert isinstance(outputQueue, Queue.Queue)
        assert callable(fd.readline)

        self.fd = fd
        self.outputQueue = outputQueue

    def run(self):
        map(self.outputQueue.put, iter(self.fd.readline, ''))

    def eof(self):
        return not self.is_alive() and self.outputQueue.empty()

    @classmethod
    def getForFd(cls, fd, start=True):
        queue = Queue.Queue()
        reader = cls(fd, queue)

        if start:
            reader.start()

        return reader, queue


process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdoutReader, stdoutQueue) = AsyncLineReader.getForFd(process.stdout)
(stderrReader, stderrQueue) = AsyncLineReader.getForFd(process.stderr)

# Keep checking queues until there is no more output.
while not stdoutReader.eof() or not stderrReader.eof():
   # Process all available lines from the stdout Queue.
   while not stdoutQueue.empty():
       line = stdoutQueue.get()
       print 'Received stdout: ' + repr(line)

       # Do stuff with stdout line.

   # Process all available lines from the stderr Queue.
   while not stderrQueue.empty():
       line = stderrQueue.get()
       print 'Received stderr: ' + repr(line)

       # Do stuff with stderr line.

   # Sleep for a short time to avoid excessive CPU use while waiting for data.
   sleep(0.05)

print "Waiting for async readers to finish..."
stdoutReader.join()
stderrReader.join()

# Close subprocess' file descriptors.
process.stdout.close()
process.stderr.close()

print "Waiting for process to exit..."
returnCode = process.wait()

if returnCode != 0:
   raise subprocess.CalledProcessError(returnCode, command)

Я просто хотел поделиться этим, так какЯ закончил на этом вопросе, пытаясь сделать что-то подобное, но ни один из ответов не решил мою проблему.Надеюсь, это кому-нибудь поможет!

Обратите внимание, что в моем случае использования внешний процесс убивает процесс, который мы Popen().

1 голос
/ 08 марта 2018

Этот PoC постоянно читает выходные данные процесса и может быть доступен при необходимости. Сохраняется только последний результат, все остальные выходные данные отбрасываются, что препятствует росту памяти PIPE:

import subprocess
import time
import threading
import Queue


class FlushPipe(object):
    def __init__(self):
        self.command = ['python', './print_date.py']
        self.process = None
        self.process_output = Queue.LifoQueue(0)
        self.capture_output = threading.Thread(target=self.output_reader)

    def output_reader(self):
        for line in iter(self.process.stdout.readline, b''):
            self.process_output.put_nowait(line)

    def start_process(self):
        self.process = subprocess.Popen(self.command,
                                        stdout=subprocess.PIPE)
        self.capture_output.start()

    def get_output_for_processing(self):
        line = self.process_output.get()
        print ">>>" + line


if __name__ == "__main__":
    flush_pipe = FlushPipe()
    flush_pipe.start_process()

    now = time.time()
    while time.time() - now < 10:
        flush_pipe.get_output_for_processing()
        time.sleep(2.5)

    flush_pipe.capture_output.join(timeout=0.001)
    flush_pipe.process.kill()

print_date.py

#!/usr/bin/env python
import time

if __name__ == "__main__":
    while True:
        print str(time.time())
        time.sleep(0.01)

output: вы можете ясно видеть, что между интервалом ~ 2,5 с ничего нет.

>>>1520535158.51
>>>1520535161.01
>>>1520535163.51
>>>1520535166.01
0 голосов
/ 25 апреля 2019

Ни один из ответов здесь не отвечает всем моим потребностям.

  1. Нет потоков для стандартного вывода (нет очередей и т. Д.)
  2. Неблокирование, так как мне нужно проверить, что происходит
  3. Используйте ТРУБУ, как мне нужно, чтобы сделать несколько вещей, например, выводить поток, записывать в файл журнала и возвращать строковую копию вывода.

Немного предыстории: я использую ThreadPoolExecutor для управления пулом потоков, каждый из которых запускает подпроцесс и выполняет их параллелизм. (В Python2.7, но это должно работать и в более новых 3.x). Я не хочу использовать потоки только для сбора выходных данных, так как хочу, чтобы как можно больше было доступно для других целей (пул из 20 процессов использовал бы только 40 потоков для запуска; 1 для потока процесса и 1 для stdout ... и больше, если вы хотите, stderr, я думаю)

Я отбрасываю множество исключений и тому подобное, так что это на основе кода, который работает в производстве. Надеюсь, я не испортил это в копии и вставке. Также, отзывы очень приветствуются!

import time
import fcntl
import subprocess
import time

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

# Make stdout non-blocking when using read/readline
proc_stdout = proc.stdout
fl = fcntl.fcntl(proc_stdout, fcntl.F_GETFL)
fcntl.fcntl(proc_stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)

def handle_stdout(proc_stream, my_buffer, echo_streams=True, log_file=None):
    """A little inline function to handle the stdout business. """
    # fcntl makes readline non-blocking so it raises an IOError when empty
    try:
        for s in iter(proc_stream.readline, ''):   # replace '' with b'' for Python 3
            my_buffer.append(s)

            if echo_streams:
                sys.stdout.write(s)

            if log_file:
                log_file.write(s)
    except IOError:
        pass

# The main loop while subprocess is running
stdout_parts = []
while proc.poll() is None:
    handle_stdout(proc_stdout, stdout_parts)

    # ...Check for other things here...
    # For example, check a multiprocessor.Value('b') to proc.kill()

    time.sleep(0.01)

# Not sure if this is needed, but run it again just to be sure we got it all?
handle_stdout(proc_stdout, stdout_parts)

stdout_str = "".join(stdout_parts)  # Just to demo

Я уверен, что здесь добавляются накладные расходы, но в моем случае это не проблема. Функционально он делает то, что мне нужно. Единственная вещь, которую я не решил, - почему это прекрасно работает для сообщений журнала, но я вижу, что некоторые print сообщения появляются позже и все сразу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...