Постоянно печатать вывод подпроцесса во время выполнения процесса


для запуска программ из моих 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"), моя программа ждет, пока процесс не будет завершен, и только тогда я получаю полный вывод моей программы. Это раздражает, если я запускаю процесс, который занимает некоторое время, чтобы закончить.

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

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

9 132

9 ответов:

можно использовать 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="")

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

чтобы напечатать вывод подпроцесса построчно, как только его буфер 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: чтение потокового ввода из подпроцесса.общаться().

@tokland

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

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)

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

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

sys.stdout.flush()

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

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)

выход: вы можете ясно видеть, что есть только выход из интервала ~2.5 s ничего между ними.

>>>1520535158.51
>>>1520535161.01
>>>1520535163.51
>>>1520535166.01

в 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

в случае если кто-то хочет читать как 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().

это работает, по крайней мере, в Питон3.4

import subprocess

process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE)
for line in process.stdout:
    print(line.decode().strip())