Ciągłe drukowanie danych Podprocesowych podczas pracy procesu

Aby uruchomić programy z moich skryptów Pythona, używam następującej metody:

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)

Więc kiedy uruchamiam taki proces jak Process.execute("mvn clean install"), Mój program czeka aż proces zostanie zakończony i dopiero wtedy otrzymuję pełny wynik mojego programu. Jest to denerwujące, jeśli prowadzę proces, który trwa chwilę, aby zakończyć.

Czy mogę pozwolić mojemu programowi napisać wyjście procesu linia po linii, przez przepytanie wyjścia procesu, zanim zakończy się w pętli lub coś w tym stylu?

* * [edytuj] Przepraszam, że nie. wyszukaj bardzo dobrze przed wysłaniem tego pytania. Gwintowanie jest w rzeczywistości kluczem. Znalazłem tutaj przykład, który pokazuje, jak to zrobić: ** Podproces Pythona.Popen z wątku

Author: Community, 2010-12-11

9 answers

Możesz użyć iter do przetwarzania linii, gdy tylko polecenie je wyświetli: lines = iter(fd.readline, ""). Oto pełny przykład pokazujący typowy przypadek użycia (dzięki @jfs za pomoc):

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="")
 193
Author: tokland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-27 22:46:13

Ok udało mi się rozwiązać go bez wątków (wszelkie sugestie, dlaczego używanie wątków byłoby lepsze, są mile widziane), używając fragmentu z tego pytania przechwytującego stdout podprocesu podczas jego działania

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)
 74
Author: ifischer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:22

Aby wydrukować wyjście podprocesu linia po linii, gdy tylko jego bufor stdout zostanie spłukany w Pythonie 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)

Uwaga: nie potrzebujesz p.poll() -- pętla kończy się po osiągnięciu eof. I nie potrzebujesz iter(p.stdout.readline, '') -- Błąd odczytu z wyprzedzeniem został naprawiony w Pythonie 3.

Zobacz także, Python: odczyt strumieniowego wejścia z podprocesu.communicate () .

 41
Author: jfs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:12

@tokland

Wypróbowałem Twój kod i poprawiłem go dla 3.4 i windows reż.cmd jest prostym poleceniem dir, zapisanym jako 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)
 4
Author: user3759376,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-25 22:31:32

Dla każdego, kto próbuje odpowiedzi na to pytanie, aby uzyskać stdout ze skryptu Pythona, zauważ, że Python buforuje swoje stdout, a zatem może zająć trochę czasu, aby zobaczyć stdout.

Można to naprawić dodając po każdym zapisie stdout w skrypcie docelowym:

sys.stdout.flush()
 3
Author: user1379351,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-21 12:40:23

Ten PoC stale odczytuje dane wyjściowe z procesu i może być dostępny w razie potrzeby. Tylko ostatni wynik jest przechowywany, wszystkie inne wyjście jest odrzucane, dlatego zapobiega wyrastaniu rury z pamięci:

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: wyraźnie widać, że jest tylko wyjście z ~2,5 s odstępu, pomiędzy którym nic nie ma.

>>>1520535158.51
>>>1520535161.01
>>>1520535163.51
>>>1520535166.01
 1
Author: Robert Nagtegaal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-08 20:02:28

W Pythonie > = 3.5 używanie subprocess.run działa dla mnie:

import subprocess

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

(uzyskanie wyniku podczas wykonywania pracy działa również bez shell=True) https://docs.python.org/3/library/subprocess.html#subprocess.run

 1
Author: user7017793,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-05 06:56:27

Jeśli ktoś chce czytać zarówno z stdout jak i stderr w tym samym czasie używając wątków, oto co wymyśliłem:

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)

Po prostu chciałem się tym podzielić, ponieważ skończyło się na tym pytaniu, próbując zrobić coś podobnego, ale żadna z odpowiedzi nie rozwiązała mojego problemu. Mam nadzieję, że to komuś pomoże!

Zauważ, że w moim przypadku, zewnętrzny proces zabija proces, który my Popen().

 0
Author: Will,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-11 00:36:14

To działa przynajmniej w Python3. 4

import subprocess

process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE)
for line in process.stdout:
    print(line.decode().strip())
 0
Author: arod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-20 01:36:58