webentwicklung-frage-antwort-db.com.de

Drucken Sie die Ausgabe des Unterprozesses ständig, während der Prozess ausgeführt wird

Um Programme aus meinen Python-Skripten zu starten, verwende ich die folgende Methode:

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)

Wenn ich also einen Prozess wie Process.execute("mvn clean install") starte, wartet mein Programm, bis der Prozess abgeschlossen ist, und erst dann bekomme ich die vollständige Ausgabe meines Programms. Das ist ärgerlich, wenn ich einen Prozess durchführe, der einige Zeit in Anspruch nimmt.

Kann ich mein Programm die Prozessausgabe Zeile für Zeile schreiben lassen, indem die Prozessausgabe abgefragt wird, bevor sie in einer Schleife oder etwas beendet wird?

** [EDIT] Es tut mir leid, dass ich nicht sehr gut nach dieser Frage gesucht habe. Einfädeln ist eigentlich der Schlüssel. Hier ein Beispiel gefunden, das zeigt, wie es geht: ** Python Subprocess.Popen aus einem Thread

142
ifischer

Sie können iter verwenden, um Zeilen zu verarbeiten, sobald der Befehl sie ausgibt: lines = iter(fd.readline, ""). Hier ist ein vollständiges Beispiel, das einen typischen Anwendungsfall zeigt (dank @jfs für das Helfen):

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="")
203
tokland

Ok, ich habe es geschafft, es ohne Threads zu lösen (alle Vorschläge, warum die Verwendung von Threads besser wäre, werden geschätzt), indem ein Ausschnitt aus dieser Frage verwendet wird. Stdout eines Unterprozesses abfangen, während er läuft

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)
76
ifischer

So drucken Sie die Ausgabe des Unterprozesses Zeile für Zeile, sobald der Puffer stdout in Python 3 geleert ist:

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)

Hinweis: Sie brauchen p.poll() nicht - die Schleife endet, wenn eof erreicht ist. Und Sie brauchen iter(p.stdout.readline, '') nicht - der Read-Ahead-Fehler wurde in Python 3 behoben.

Siehe auch Python: Lese-Streaming-Eingabe von subprocess.communicate () .

47
jfs

@tokland

sie haben Ihren Code ausprobiert und für 3.4 und Windows korrigiert dir.cmd ist ein einfacher Befehl, der als cmd-Datei gespeichert wird

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
user3759376

Jeder, der versucht, die Antworten auf diese Frage zu erhalten, um den Standardwert aus einem Python-Skript zu erhalten, stellt fest, dass Python seinen Standardwert puffert. Daher kann es eine Weile dauern, bis der Standardwert angezeigt wird.

Sie können dies korrigieren, indem Sie nach jedem stdout-Schreiben im Zielskript Folgendes hinzufügen:

sys.stdout.flush()
3
user1379351

In Python> = 3.5 funktioniert subprocess.run für mich:

import subprocess

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

(Das Abrufen der Ausgabe während der Ausführung funktioniert auch ohne Shell=True) https://docs.python.org/3/library/subprocess.html#subprocess.run

2
user7017793

Für den Fall, dass jemand mit Threads gleichzeitig stdout und stderr lesen möchte, habe ich Folgendes gefunden:

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)

Ich wollte das nur teilen, da ich bei dieser Frage versuche, etwas Ähnliches zu tun, aber keine der Antworten löste mein Problem. Hoffentlich hilft es jemandem!

Beachten Sie, dass in meinem Anwendungsfall ein externer Prozess den Prozess beendet, den wir Popen() verwenden.

1
Will

Dieser PoC liest ständig die Ausgabe eines Prozesses und kann bei Bedarf aufgerufen werden. Nur das letzte Ergebnis wird beibehalten, alle anderen Ausgaben werden verworfen und verhindern, dass der PIPE zu viel Speicherplatz bekommt:

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 __== "__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 __== "__main__":
    while True:
        print str(time.time())
        time.sleep(0.01)

ausgabe: Sie können deutlich sehen, dass nur im ~ 2,5s-Intervall nichts dazwischen liegt.

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

Um die ursprüngliche Frage zu beantworten, lenken Sie IMO am besten einfach mit dem Unterprozess stdout direkt zur stdout Ihres Programms. (Optional kann dies auch für stderr wie im folgenden Beispiel ausgeführt werden.)

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

Dies funktioniert zumindest in Python3.4

import subprocess

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

Keine der Antworten hier hat alle meine Bedürfnisse angesprochen.

  1. Keine Threads für stdout (auch keine Queues etc)
  2. Nicht blockierend, da ich nach anderen Dingen suchen muss
  3. Verwenden Sie PIPE, wenn ich mehrere Aufgaben ausführen musste, z. Ausgabe streamen, in eine Protokolldatei schreiben und eine String-Kopie der Ausgabe zurückgeben.

Ein kleiner Hintergrund: Ich verwalte mit einem ThreadPoolExecutor einen Pool von Threads, wobei jeder einen Unterprozess startet und diese gleichzeitig ausführt. (In Python2.7, aber dies sollte auch in neueren 3.x funktionieren). Ich möchte keine Threads nur zum Sammeln von Ausgaben verwenden, da ich so viele wie möglich für andere Zwecke zur Verfügung haben möchte (ein Pool von 20 Prozessen würde 40 Threads nur zum Ausführen verwenden; 1 für den Prozessthread und 1 für stdout ... und mehr, wenn Sie stderr wollen, denke ich)

Ich ziehe eine Menge Ausnahmen zurück und solche hier, also ist dies basierend auf Code, der in der Produktion funktioniert. Hoffentlich habe ich es beim Kopieren und Einfügen nicht ruiniert. Auch Feedback sehr willkommen!

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

Ich bin sicher, dass hier zusätzliche Kosten anfallen, aber das ist in meinem Fall kein Problem. Funktionell macht es was ich brauche. Das einzige, was ich nicht gelöst habe, ist, warum dies für Protokollnachrichten perfekt funktioniert, aber ich sehe, dass einige print Nachrichten später und auf einmal angezeigt werden.

0
Rafe