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

Чтобы запускать программы из моих сценариев 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] Извините, я не очень хорошо разбирался перед публикацией этого вопроса. Threading - это на самом деле ключ. Нашел здесь пример, который показывает, как это сделать: ** Python Subprocess.Popen из потока

Ответ 1

Вы можете использовать iter для обработки строк, как только команда lines = iter(fd.readline, "") их: 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="")

Ответ 2

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

Ответ 3

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

Ответ 4

@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)

Ответ 5

В 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

Ответ 6

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

p = Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
p.communicate()

Ответ 7

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

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

sys.stdout.flush()

Ответ 8

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

Ответ 9

Этот 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)

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

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

Ответ 10

Я считаю, что вы ищете subprocess.call(). Вот ссылка на документы - https://docs.python.org/2/library/subprocess.html#subprocess.call. Из документов:

stdin, stdout и stderr задают стандартный ввод исполняемых программ, стандартный вывод и стандартные файлы файлов ошибок соответственно. Допустимыми значениями являются PIPE, существующий файловый дескриптор (положительное целое число), существующий файловый объект и None. PIPE указывает, что должен быть создан новый канал для ребенка. При настройках по умолчанию "Нет" перенаправление не произойдет; дескрипторы файлов childs будут унаследованы от родителя. Кроме того, stderr может быть STDOUT, что указывает, что данные stderr из дочернего процесса должны быть записаны в тот же файл, что и для stdout.

Итак, exitcode = subprocess.call(command, shell=True) выполнит задание в одной строке:) Вот пример:

test_command = "ls -ltrah"
exitcode = subprocess.call(test_command, stderr=subprocess.STDOUT, 
shell=True)
print("exit code = " + str(exitcode))

Ответ 11

Это работает по крайней мере в Python3.4

import subprocess

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

Ответ 12

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

  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 появляются позже и все сразу.

Ответ 13

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

import subprocess
import sys

def execute(command):
    subprocess.check_call(command, shell=True, stdout=sys.stdout, stderr=subprocess.STDOUT)

Здесь мы просто указываем подпроцесс на наш собственный stdout и используем существующие API-интерфейсы success или исключений.

Ответ 14

В Python 3.6 я использовал это:

import subprocess

cmd = "command"
output = subprocess.call(cmd, shell=True)
print(process)