it-swarm.com.de

live-Ausgabe vom Unterprozessbefehl

Ich benutze ein Python-Skript als Treiber für einen Hydrodynamik-Code. Wenn es an der Zeit ist, die Simulation auszuführen, verwende ich subprocess.Popen, um den Code auszuführen, sammle die Ausgabe von stdout und stderr in einen subprocess.PIPE ---. Dann kann ich die Ausgabeinformationen drucken (und in einer Protokolldatei speichern) und überprüfen, ob Irgendwelche Fehler. Das Problem ist, ich habe keine Ahnung, wie sich der Code entwickelt. Wenn ich es direkt von der Kommandozeile aus laufe, gibt es eine Ausgabe darüber, um welche Iteration es sich handelt, um wie viel Uhr, was der nächste Zeitschritt ist usw.

Gibt es eine Möglichkeit, die Ausgabe zu speichern (für die Protokollierung und Fehlerprüfung) und auch eine Live-Streaming-Ausgabe zu erzeugen?

Der relevante Abschnitt meines Codes:

ret_val = subprocess.Popen( run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, Shell=True )
output, errors = ret_val.communicate()
log_file.write(output)
print output
if( ret_val.returncode ):
    print "RUN failed\n\n%s\n\n" % (errors)
    success = False

if( errors ): log_file.write("\n\n%s\n\n" % errors)

Ursprünglich habe ich den run_command durch tee weitergeleitet, so dass eine Kopie direkt in die Protokolldatei ging und der Stream immer noch direkt an das Terminal ausgegeben wird. Auf diese Weise kann ich jedoch keine Fehler speichern (meines Wissens nach).


Bearbeiten:

Vorübergehende Lösung:

ret_val = subprocess.Popen( run_command, stdout=log_file, stderr=subprocess.PIPE, Shell=True )
while not ret_val.poll():
    log_file.flush()

führen Sie dann in einem anderen Terminal tail -f log.txt (s.t. log_file = 'log.txt') aus.

134
DilithiumMatrix

Sie haben zwei Möglichkeiten, dies zu tun, indem Sie entweder einen Iterator aus den Funktionen read oder readline erstellen und tun:

import subprocess
import sys
with open('test.log', 'w') as f:
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for c in iter(lambda: process.stdout.read(1), ''):  # replace '' with b'' for Python 3
        sys.stdout.write(c)
        f.write(c)

oder

import subprocess
import sys
with open('test.log', 'w') as f:
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for line in iter(process.stdout.readline, ''):  # replace '' with b'' for Python 3
        sys.stdout.write(line)
        f.write(line)

Oder Sie können eine reader- und eine writer-Datei erstellen. Übergeben Sie die writer an die Popen und lesen Sie sie aus der reader

import io
import time
import subprocess
import sys

filename = 'test.log'
with io.open(filename, 'wb') as writer, io.open(filename, 'rb', 1) as reader:
    process = subprocess.Popen(command, stdout=writer)
    while process.poll() is None:
        sys.stdout.write(reader.read())
        time.sleep(0.5)
    # Read the remaining
    sys.stdout.write(reader.read())

Auf diese Weise werden die Daten sowohl in den test.log als auch in die Standardausgabe geschrieben. 

Der einzige Vorteil des Dateiverfahrens besteht darin, dass Ihr Code nicht blockiert. So können Sie in der Zwischenzeit tun, was Sie möchten, und aus der Variablen reader lesen, wann immer Sie möchten. Wenn Sie PIPE verwenden, werden read- und readline-Funktionen blockiert, bis entweder ein Zeichen in die Pipe oder eine Zeile in die Pipe geschrieben wird.

123
Viktor Kerkez

Executive Summary (oder "tl; dr" -Version): Es ist einfach, wenn es höchstens einen subprocess.PIPE gibt, sonst ist es schwierig.

Es könnte Zeit sein, ein wenig zu erklären, wie subprocess.Popen seine Sache tut.

(Vorbehalt: Dies ist für Python 2.x, obwohl 3.x ähnlich ist, und ich bin bei der Windows-Variante ziemlich unscharf. Ich verstehe das POSIX-Material viel besser.)

Die Popen - Funktion muss etwas gleichzeitig mit null bis drei E/A-Strömen arbeiten. Diese werden wie üblich mit stdin, stdout und stderr bezeichnet.

Sie können Folgendes anbieten:

  • None zeigt an, dass Sie den Stream nicht umleiten möchten. Es wird diese stattdessen wie üblich erben. Beachten Sie, dass dies zumindest auf POSIX-Systemen nicht bedeutet, dass sys.stdout von Python verwendet wird, nur Pythons actual stdout; siehe Demo am Ende.
  • Ein int Wert. Dies ist ein "roher" Dateideskriptor (mindestens in POSIX). (Randbemerkung: PIPE und STDOUT sind tatsächlich ints, sind aber "unmögliche" Deskriptoren -1 und -2.)
  • Ein Stream - eigentlich jedes Objekt mit der Methode fileno. Popen findet den Deskriptor für diesen Stream mithilfe von stream.fileno() und fährt dann mit einem int-Wert fort.
  • subprocess.PIPE gibt an, dass Python eine Pipe erstellen soll.
  • subprocess.STDOUT (nur für stderr): Weisen Sie Python an, denselben Deskriptor zu verwenden wie für stdout. Dies ist nur sinnvoll, wenn Sie einen Wert (nicht -None) für stdout angegeben haben, und selbst dann ist es nur required , wenn Sie stdout=subprocess.PIPE setzen. (Andernfalls können Sie einfach dasselbe Argument angeben, das Sie für stdout angegeben haben, z. B. Popen(..., stdout=stream, stderr=stream).)

Die einfachsten Fälle (keine Pfeifen)

Wenn Sie nichts umleiten (belassen Sie alle drei als Standardwert None oder geben Sie den expliziten Wert None an, ist es für Pipe ganz einfach. Es muss nur den Subprozess ablaufen lassen und laufen lassen. Oder, wenn Sie zu einer nicht-PIPE - einer int - oder einer __Funktion eines Streams __-- umleiten, ist es immer noch einfach, da das Betriebssystem die ganze Arbeit erledigt. Python muss lediglich den Unterprozess abschalten und stdin, stdout und/oder stderr mit den bereitgestellten Dateideskriptoren verbinden.

Der immer noch einfache Fall: eine Pfeife

Wenn Sie nur einen Stream umleiten, ist es für Pipe immer noch ziemlich einfach. Lass uns einen Stream auswählen und schauen.

Angenommen, Sie möchten stdin angeben, aber stdout und stderr werden nicht weitergeleitet oder gehen zu einem Dateideskriptor über. Als übergeordneter Prozess muss Ihr Python-Programm lediglich fileno() verwenden, um Daten über die Pipe zu senden. Sie können dies selbst tun, z.

proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc

oder Sie können die stdin-Daten an write() übergeben, die dann den oben angegebenen stdin.write ausführt. Da keine Ausgabe zurückkommt, hat proc.communicate() nur noch eine weitere Aufgabe: Es schließt auch die Pipe für Sie. (Wenn Sie nicht communicate() aufrufen, müssen Sie proc.communicate() aufrufen, um die Pipe zu schließen, damit der Unterprozess weiß, dass keine Daten mehr durchlaufen.)

Angenommen, Sie möchten stdout erfassen, stdin und stderr jedoch allein lassen. Wieder ist es ganz einfach: Rufen Sie einfach proc.stdin.close() (oder gleichwertig) auf, bis keine Ausgabe mehr erfolgt. Da proc.stdout.read() ein normaler Python-E/A-Stream ist, können Sie alle normalen Konstrukte darauf verwenden, z.

for line in proc.stdout:

oder wiederum können Sie proc.stdout() verwenden, was einfach die proc.communicate() für Sie erledigt.

Wenn Sie nur stderr erfassen möchten, funktioniert es genauso wie mit stdout.

Es gibt noch einen Trick, bevor die Dinge hart werden. Angenommen, Sie möchten stdout und stderr aber in derselben Pipe wie stdout erfassen:

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

In diesem Fall subprocess "Cheats"! Nun, es muss dies tun, also nicht wirklich betrügen: Es startet den Unterprozess, wobei sowohl stdout als auch stderr in den (einzigen) Pipe-Deskriptor geleitet werden, der auf den übergeordneten Prozess (Python) zurückgreift. Auf der übergeordneten Seite gibt es nur einen einzigen Pipe-Deskriptor zum Lesen der Ausgabe. Alle "stderr" -Ausgaben werden in proc.stdout angezeigt. Wenn Sie read() aufrufen, ist das stderr-Ergebnis (zweiter Wert im Tuple) None, keine Zeichenfolge.

Die harten Fälle: zwei oder mehr Rohre

Die Probleme treten alle auf, wenn Sie mindestens zwei Rohre verwenden möchten. Tatsächlich hat der subprocess-Code dieses Bit:

def communicate(self, input=None):
    ...
    # Optimization: If we are only using one pipe, or no pipe at
    # all, using select() or threads is unnecessary.
    if [self.stdin, self.stdout, self.stderr].count(None) >= 2:

Aber leider haben wir hier mindestens zwei und vielleicht drei verschiedene Pipes gemacht, also gibt die Funktion entweder 1 oder 0 zurück. Wir müssen es auf die harte Tour machen.

Unter Windows werden threading.Thread zum Sammeln der Ergebnisse für self.stdout und self.stderr verwendet, und der übergeordnete Thread liefert self.stdin-Eingabedaten (und schließt dann die Pipe).

Bei POSIX wird poll verwendet, falls verfügbar, andernfalls select, um die Ausgabe zu sammeln und die Standardeingabe zu liefern. All dies läuft im (einzigen) übergeordneten Prozess/Thread ab.Hier sind Threads oder Poll/Select erforderlich, um Deadlock zu vermeiden. Angenommen, wir haben alle drei Streams auf drei separate Pipes umgeleitet. Angenommen, es gibt eine kleine Grenze dafür, wie viele Daten in eine Pipe gestopft werden können, bevor der Schreibvorgang angehalten wird, und warten, bis der Lesevorgang die Pipe vom anderen Ende "reinigt". Lassen Sie uns dieses kleine Limit auf ein Byte setzen, nur zur Veranschaulichung. (Dies ist in der Tat so, wie die Dinge funktionieren, außer dass das Limit viel größer als ein Byte ist.).

Wenn der übergeordnete (Python) -Prozess versucht, mehrere Bytes zu schreiben, beispielsweise 'go\n'zu proc.stdin, geht das erste Byte ein und das zweite Byte bewirkt, dass der Python-Prozess angehalten wird. Er wartet darauf, dass der Unterprozess das erste Byte liest und die Pipe leert.

Nehmen Sie an, der Unterprozess beschließt, ein freundliches "Hallo! Keine Panik!" Zu drucken. Gruß. Die H geht in ihre stdout-Pipe, aber die e bewirkt, dass sie angehalten wird und wartet, bis ihr Elternteil H liest.

Jetzt stecken wir fest: Der Python-Prozess schläft und wartet, bis er "go" beendet hat, und der Unterprozess schläft ebenfalls und wartet, bis er "Hallo! Keine Panik!".

Der Code subprocess.Popen vermeidet dieses Problem beim Einfädeln oder Auswählen/Abfragen. Wenn Bytes über die Pipes gehen können, gehen sie. Wenn dies nicht möglich ist, muss nur ein Thread (nicht der gesamte Prozess) in den Ruhezustand versetzt werden - oder im Fall von select/poll wartet der Python-Prozess gleichzeitig auf "Kann schreiben" oder "Daten verfügbar" und schreibt in die Standardeingabedatei des Prozesses Nur wenn Platz vorhanden ist, und nur wenn Daten bereit sind, werden Stdout und/oder Stderr gelesen. Der proc.communicate()-Code (eigentlich _communicate, in dem die haarigen Fälle behandelt werden) wird zurückgegeben, sobald alle stdin-Daten (falls vorhanden) gesendet wurden und alle stdout- und/oder stderr-Daten gesammelt wurden.

Wenn Sie sowohl stdout als auch stderr an zwei verschiedenen Pipes lesen möchten (unabhängig von einer stdin-Weiterleitung), müssen Sie auch Deadlock vermeiden. Das Deadlock-Szenario unterscheidet sich hier - es tritt ein, wenn der Unterprozess etwas in stderr schreibt, während Sie Daten aus stdout oder umgekehrt abrufen -, aber es ist immer noch da.

.


Die Demo

Ich habe versprochen zu demonstrieren, dass Python subprocesses nicht umgeleitet in das zugrunde liegende stdout schreibt, nicht sys.stdout. Hier ist also ein Code:

from cStringIO import StringIO import os import subprocess import sys def show1(): print 'start show1' save = sys.stdout sys.stdout = StringIO() print 'sys.stdout being buffered' proc = subprocess.Popen(['echo', 'hello']) proc.wait() in_stdout = sys.stdout.getvalue() sys.stdout = save print 'in buffer:', in_stdout def show2(): print 'start show2' save = sys.stdout sys.stdout = open(os.devnull, 'w') print 'after redirect sys.stdout' proc = subprocess.Popen(['echo', 'hello']) proc.wait() sys.stdout = save show1() show2()

$ python out.py
start show1
hello
in buffer: sys.stdout being buffered

start show2
hello

(Wenn Sie den Datei-Deskriptor-1 von Python umleiten, folgt der Unterprozess dieser Umleitung. /. Der Aufruf count(None) erzeugt einen Stream, dessen proc.communicate() größer als 2 ist.).

(If you redirect Python's file-descriptor-1, the subprocess /- will follow that redirection. The open(os.devnull, 'w') call produces a stream whose fileno() is greater than 2.)

73
torek

Wir können auch den Standard-Datei-Iterator zum Lesen von stdout verwenden, anstatt das iter-Konstrukt mit readline () zu verwenden. 

import subprocess
import sys
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for line in process.stdout:
    sys.stdout.write(line)
13
Jughead

Wenn Sie Bibliotheken von Drittanbietern verwenden können, können Sie möglicherweise etwas wie sarge (Offenlegung: Ich bin der Betreuer) verwenden. Diese Bibliothek ermöglicht den nicht blockierenden Zugriff auf Ausgabeströme aus Unterprozessen - sie liegt über dem Modul subprocess.

10
Vinay Sajip

Eine gute, aber "schwergewichtige" Lösung ist die Verwendung von Twisted - siehe unten.

Wenn Sie bereit sind, nur mit etwas in dieser Richtung zu leben, sollten Sie Folgendes tun:

import subprocess
import sys
popenobj = subprocess.Popen(["ls", "-Rl"], stdout=subprocess.PIPE)
while not popenobj.poll():
   stdoutdata = popenobj.stdout.readline()
   if stdoutdata:
      sys.stdout.write(stdoutdata)
   else:
      break
print "Return code", popenobj.returncode

(Wenn Sie read () verwenden, wird versucht, die gesamte "Datei" zu lesen, was nicht nützlich ist. Was wir hier wirklich verwenden könnten, ist etwas, das alle Daten liest, die gerade in der Pipe sind.)

Man könnte auch versuchen, dies durch Einfädeln zu erreichen, z.

import subprocess
import sys
import threading

popenobj = subprocess.Popen("ls", stdout=subprocess.PIPE, Shell=True)

def stdoutprocess(o):
   while True:
      stdoutdata = o.stdout.readline()
      if stdoutdata:
         sys.stdout.write(stdoutdata)
      else:
         break

t = threading.Thread(target=stdoutprocess, args=(popenobj,))
t.start()
popenobj.wait()
t.join()
print "Return code", popenobj.returncode

Jetzt könnten wir möglicherweise auch stderr hinzufügen, indem wir zwei Threads haben.

Beachten Sie jedoch, dass die Unterprozessdokumente davon abraten, diese Dateien direkt zu verwenden, und empfehlen, communicate() zu verwenden (hauptsächlich bei Deadlocks, von denen ich glaube, dass sie oben kein Problem darstellen), und die Lösungen sind ein wenig klunkig, sodass es wirklich so aussieht ) das Subprozessmodul ist nicht ganz der Aufgabe gewachsen (siehe auch: http://www.python.org/dev/peps/pep-3145/ ) und wir brauchen etwas anderes anschauen.

Eine aufwändigere Lösung ist die Verwendung von Twisted wie hier gezeigt: https://twistedmatrix.com/documents/11.1.0/core/howto/process.html

Die Art und Weise, wie Sie dies mit Twisted tun, besteht darin, Ihren Prozess mit reactor.spawnprocess() zu erstellen und ProcessProtocol bereitzustellen, der dann die Ausgabe asynchron verarbeitet. Der Twisted-Beispielcode Python ist hier: https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.py

2
Guy Sirton

Es sieht so aus, als ob die Ausgabe mit Zeilenpufferung für Sie funktionieren würde. (Vorsichtsmaßnahme: Es wurde nicht getestet.) Dadurch wird nur der Standardwert des Unterprozesses in Echtzeit angezeigt. Wenn Sie in Echtzeit sowohl stderr als auch stdout haben möchten, müssen Sie etwas komplexeres mit select tun.

proc = subprocess.Popen(run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, Shell=True)
while proc.poll() is None:
    line = proc.stdout.readline()
    print line
    log_file.write(line + '\n')
# Might still be data on stdout at this point.  Grab any
# remainder.
for line in proc.stdout.read().split('\n'):
    print line
    log_file.write(line + '\n')
# Do whatever you want with proc.stderr here...
1
Alp

Lösung 1: Protokollieren Sie stdout UND stderr gleichzeitig in Echtzeit

Eine einfache Lösung, die sowohl stdout als auch stderr gleichzeitig in Echtzeit protokolliert.

import subprocess as sp
from concurrent.futures import ThreadPoolExecutor


def log_popen_pipe(p, pipe_name):

    while p.poll() is None:
        line = getattr(p, pipe_name).readline()
        log_file.write(line)


with sp.Popen(my_cmd, stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    with ThreadPoolExecutor(2) as pool:
        r1 = pool.submit(log_popen_pipe, p, 'stdout')
        r2 = pool.submit(log_popen_pipe, p, 'stderr')
        r1.result()
        r2.result()

Lösung 2: Erstellen Sie einen Iterator, der zeilenweise und gleichzeitig in Echtzeit stdout und stderr zurückgibt

Hier erstellen wir eine Funktion read_popen_pipes(), mit der Sie in Echtzeit über beide Pipes (stdout/stderr) iterieren können:

import subprocess as sp
from queue import Queue, Empty
from concurrent.futures import ThreadPoolExecutor


def enqueue_output(file, queue):
    for line in iter(file.readline, ''):
        queue.put(line)
    file.close()


def read_popen_pipes(p):

    with ThreadPoolExecutor(2) as pool:
        q_stdout, q_stderr = Queue(), Queue()

        pool.submit(enqueue_output, p.stdout, q_stdout)
        pool.submit(enqueue_output, p.stderr, q_stderr)

        while True:

            if p.poll() is not None and q_stdout.empty() and q_stderr.empty():
                break

            out_line = err_line = ''

            try:
                out_line = q_stdout.get_nowait()
                err_line = q_stderr.get_nowait()
            except Empty:
                pass

            yield (out_line, err_line)


with sp.Popen(my_cmd, stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    for out_line, err_line in read_popen_pipes(p):
        print(out_line, end='')
        print(err_line, end='')

    return p.poll()

1
Rotareti

Ausgehend von all dem schlage ich eine leicht modifizierte Version (python3) vor:

  • while-Schleife beim Aufruf von readline (Die vorgeschlagene Iter-Lösung schien für immer zu blockieren - Python 3, Windows 7)
  • strukturiert, so dass die Handhabung von gelesenen Daten nicht dupliziert werden muss, nachdem die Abfrage nicht -None zurückgegeben hat
  • stderr wurde in stdout geleitet, so dass beide Ausgangsausgaben gelesen werden
  • Code hinzugefügt, um den Exit-Wert von cmd zu erhalten.

Code:

import subprocess
proc = subprocess.Popen(cmd, Shell=True, stdout=subprocess.PIPE,
                        stderr=subprocess.STDOUT, universal_newlines=True)
while True:
    rd = proc.stdout.readline()
    print(rd, end='')  # and whatever you want to do...
    if not rd:  # EOF
        returncode = proc.poll()
        if returncode is not None:
            break
        time.sleep(0.1)  # cmd closed stdout, but not exited yet

# You may want to check on ReturnCode here
0
mrtnlrsn

Warum nicht stdout direkt auf sys.stdout setzen? Und wenn Sie auch in ein Protokoll ausgeben müssen, können Sie einfach die Schreibmethode von f überschreiben.

import sys
import subprocess

class SuperFile(open.__class__):

    def write(self, data):
        sys.stdout.write(data)
        super(SuperFile, self).write(data)

f = SuperFile("log.txt","w+")       
process = subprocess.Popen(command, stdout=f, stderr=f)
0
xaav

Neben all diesen Antworten könnte ein einfacher Ansatz wie folgt aussehen:

process = subprocess.Popen(your_command, stdout=subprocess.PIPE)

while process.stdout.readable():
    line = process.stdout.readline()

    if not line:
        break

    print(line.strip())

Durchlaufen Sie den lesbaren Stream so lange, bis er lesbar ist. Wenn er ein leeres Ergebnis erhält, stoppen Sie ihn.

Der Schlüssel hier ist, dass readline() eine Zeile (mit \n am Ende) zurückgibt, solange es eine Ausgabe gibt und leer ist, wenn sie wirklich am Ende ist.

Hoffe das hilft jemandem.

0
kabirbaidhya

Keine der Pythonic-Lösungen hat für mich funktioniert. Es stellte sich heraus, dass proc.stdout.read() oder ähnliches für immer blockieren kann.

Deshalb benutze ich tee so:

subprocess.run('./my_long_running_binary 2>&1 | tee -a my_log_file.txt && exit ${PIPESTATUS}', Shell=True, check=True, executable='/bin/bash')

Diese Lösung ist praktisch, wenn Sie bereits Shell=True verwenden.

${PIPESTATUS} erfasst den Erfolgsstatus der gesamten Befehlskette (nur in Bash verfügbar). Wenn ich den && exit ${PIPESTATUS} weglasse, würde dies immer Null zurückgeben, da tee niemals fehlschlägt.

unbuffer kann erforderlich sein, um jede Zeile sofort in das Terminal zu drucken, anstatt zu lange zu warten, bis der "Pipe Buffer" gefüllt ist. Unbuffer schluckt jedoch den Exit-Status von assert (SIG Abort) ...

2>&1 protokolliert auch den Stderror in der Datei.

0
Mike76

Hier ist eine Klasse, die ich in einem meiner Projekte verwende. Er leitet die Ausgabe eines Unterprozesses an das Protokoll weiter. Zuerst habe ich versucht, die write-Methode einfach zu überschreiben, aber das funktioniert nicht, da der Unterprozess sie nie nennen wird (die Umleitung erfolgt auf Ebene des Fidesescriptors). Ich verwende also meine eigene Pipe, ähnlich wie im Subprozess-Modul. Dies hat den Vorteil, dass die gesamte Protokollierungs-/Drucklogik im Adapter gekapselt wird, und Sie können Instanzen des Loggers einfach an Popen: subprocess.Popen("/path/to/binary", stderr = LogAdapter("foo")) übergeben.

class LogAdapter(threading.Thread):

    def __init__(self, logname, level = logging.INFO):
        super().__init__()
        self.log = logging.getLogger(logname)
        self.readpipe, self.writepipe = os.pipe()

        logFunctions = {
            logging.DEBUG: self.log.debug,
            logging.INFO: self.log.info,
            logging.WARN: self.log.warn,
            logging.ERROR: self.log.warn,
        }

        try:
            self.logFunction = logFunctions[level]
        except KeyError:
            self.logFunction = self.log.info

    def fileno(self):
        #when fileno is called this indicates the subprocess is about to fork => start thread
        self.start()
        return self.writepipe

    def finished(self):
       """If the write-filedescriptor is not closed this thread will
       prevent the whole program from exiting. You can use this method
       to clean up after the subprocess has terminated."""
       os.close(self.writepipe)

    def run(self):
        inputFile = os.fdopen(self.readpipe)

        while True:
            line = inputFile.readline()

            if len(line) == 0:
                #no new data was added
                break

            self.logFunction(line.strip())

Wenn Sie keine Protokollierung benötigen, sondern einfach print() verwenden möchten, können Sie offensichtlich große Teile des Codes entfernen und die Klasse kürzer halten. Sie können es auch um eine __enter__- und __exit__-Methode erweitern und finished in __exit__ aufrufen, damit Sie es problemlos als Kontext verwenden können.

0
t.animal

Ähnlich wie die vorherigen Antworten, aber die folgende Lösung funktionierte für mich unter Windows mit Python3, um eine übliche Methode zum Drucken und Anmelden in Echtzeit bereitzustellen ( Getting-Realtime-Ausgabe-using-Python ):

def print_and_log(command, logFile):
    with open(logFile, 'wb') as f:
        command = subprocess.Popen(command, stdout=subprocess.PIPE, Shell=True)

        while True:
            output = command.stdout.readline()
            if not output and command.poll() is not None:
                f.close()
                break
            if output:
                f.write(output)
                print(str(output.strip(), 'utf-8'), flush=True)
        return command.poll()
0
scottysbasement

Alle oben genannten Lösungen, die ich ausprobiert habe, scheiterten entweder daran, die Ausgabe von stderr und stdout (mehrere Pipes) zu trennen, oder sie blockierten für immer, wenn der OS-Pufferspeicher voll war. Dies ist der Fall, wenn der Befehl, den Sie ausführen, zu schnelle Ausgaben ausführt (es gibt eine Warnung für Python) Umfrage () Handbuch des Unterprozesses). Der einzige zuverlässige Weg, den ich gefunden habe, war durch select, aber dies ist eine reine Posix-Lösung:

import subprocess
import sys
import os
import select
# returns command exit status, stdout text, stderr text
# rtoutput: show realtime output while running
def run_script(cmd,rtoutput=0):
    p = subprocess.Popen(cmd, Shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    poller = select.poll()
    poller.register(p.stdout, select.POLLIN)
    poller.register(p.stderr, select.POLLIN)

    coutput=''
    cerror=''
    fdhup={}
    fdhup[p.stdout.fileno()]=0
    fdhup[p.stderr.fileno()]=0
    while sum(fdhup.values()) < len(fdhup):
        try:
            r = poller.poll(1)
        except select.error, err:
            if err.args[0] != EINTR:
                raise
            r=[]
        for fd, flags in r:
            if flags & (select.POLLIN | select.POLLPRI):
                c = os.read(fd, 1024)
                if rtoutput:
                    sys.stdout.write(c)
                    sys.stdout.flush()
                if fd == p.stderr.fileno():
                    cerror+=c
                else:
                    coutput+=c
            else:
                fdhup[fd]=1
    return p.poll(), coutput.strip(), cerror.strip()
0
sivann

Ich denke, dass die subprocess.communicate-Methode ein wenig irreführend ist: Sie füllt tatsächlich die stdout und stderr, die Sie in subprocess.Popen angeben.

Wenn Sie jedoch aus dem subprocess.PIPE, den Sie den stdout - und stderr -Parametern von subprocess.Popen zur Verfügung stellen können, lesen Sie die OS-Pufferspeicher und blockieren Ihre App (insbesondere wenn Sie mehrere Prozesse/Threads verwenden, die subprocess verwenden müssen ).

Meine vorgeschlagene Lösung besteht darin, die Dateien stdout und stderr mit Dateien zu versehen - und den Inhalt der Dateien zu lesen, anstatt aus dem Deadlocking PIPE zu lesen. Diese Dateien können tempfile.NamedTemporaryFile() sein, auf die auch während des Schreibens von subprocess.communicate zum Lesen zugegriffen werden kann. 

Nachfolgend finden Sie eine Beispielnutzung:

        try:
            with ProcessRunner(('python', 'task.py'), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner:
                for out in process_runner:
                    print(out)
        catch ProcessError as e:
            print(e.error_message)
            raise

Und dies ist der Quellcode, der bereit ist, verwendet zu werden mit so vielen Kommentaren, wie ich liefern könnte, um zu erklären, was er tut:

Wenn Sie Python 2 verwenden, installieren Sie zunächst die neueste Version des Pakets subprocess32 von pypi.


import os
import sys
import threading
import time
import tempfile

if os.name == 'posix' and sys.version_info[0] < 3:
    # Support python 2
    import subprocess32 as subprocess
else:
    # Get latest and greatest from python 3
    import subprocess


class ProcessError(Exception):
    """Base exception for errors related to running the process"""


class ProcessTimeout(ProcessError):
    """Error that will be raised when the process execution will exceed a timeout"""


class ProcessRunner(object):
    def __init__(self, args, env=None, timeout=None, bufsize=-1, seconds_to_wait=0.25, **kwargs):
        """
        Constructor facade to subprocess.Popen that receives parameters which are more specifically required for the
        Process Runner. This is a class that should be used as a context manager - and that provides an iterator
        for reading captured output from subprocess.communicate in near realtime.

        Example usage:


        try:
            with ProcessRunner(('python', task_file_path), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner:
                for out in process_runner:
                    print(out)
        catch ProcessError as e:
            print(e.error_message)
            raise

        :param args: same as subprocess.Popen
        :param env: same as subprocess.Popen
        :param timeout: same as subprocess.communicate
        :param bufsize: same as subprocess.Popen
        :param seconds_to_wait: time to wait between each readline from the temporary file
        :param kwargs: same as subprocess.Popen
        """
        self._seconds_to_wait = seconds_to_wait
        self._process_has_timed_out = False
        self._timeout = timeout
        self._process_done = False
        self._std_file_handle = tempfile.NamedTemporaryFile()
        self._process = subprocess.Popen(args, env=env, bufsize=bufsize,
                                         stdout=self._std_file_handle, stderr=self._std_file_handle, **kwargs)
        self._thread = threading.Thread(target=self._run_process)
        self._thread.daemon = True

    def __enter__(self):
        self._thread.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self._thread.join()
        self._std_file_handle.close()

    def __iter__(self):
        # read all output from stdout file that subprocess.communicate fills
        with open(self._std_file_handle.name, 'r') as stdout:
            out = stdout.readline()
            # If we've reached the sentinel, then it means that we're done with yielding data
            while not self._process_done:
                out_without_trailing_whitespaces = out.rstrip()
                if out_without_trailing_whitespaces:
                    # yield stdout data without trailing \n
                    yield out_without_trailing_whitespaces
                else:
                    # if there is nothing to read, then please wait a tiny little bit
                    time.sleep(self._seconds_to_wait)

                # continue reading as long as there is still data coming from stdout
                out = stdout.readline()

        if self._process_has_timed_out:
            raise ProcessTimeout('Process has timed out')

        if self._process.returncode != 0:
            raise ProcessError('Process has failed')

    def _run_process(self):
        try:
            # Start gathering information (stdout and stderr) from the opened process
            self._process.communicate(timeout=self._timeout)
            # Graceful termination of the opened process
            self._process.terminate()
        except subprocess.TimeoutExpired:
            self._process_has_timed_out = True
            # Force termination of the opened process
            self._process.kill()

        self._process_done = True

    @property
    def return_code(self):
        return self._process.returncode


0
Victor Klapholz