Lesen Sie die Streaming-Eingabe von subprocess.communicate ()


83

Ich verwende Pythons subprocess.communicate(), um stdout aus einem Prozess zu lesen, der ungefähr eine Minute lang ausgeführt wird.

Wie kann ich jede Zeile dieses Prozesses stdoutin Streaming- Form ausdrucken , damit ich die Ausgabe so sehen kann, wie sie generiert wurde, aber trotzdem den Prozess blockieren kann, bevor ich fortfahre?

subprocess.communicate() scheint alle Ausgaben auf einmal zu geben.


Antworten:


44

Bitte beachten Sie, dass ich die Methode von JF Sebastian (unten) für besser halte .


Hier ist ein einfaches Beispiel (ohne Überprüfung auf Fehler):

import subprocess
proc = subprocess.Popen('ls',
                       shell=True,
                       stdout=subprocess.PIPE,
                       )
while proc.poll() is None:
    output = proc.stdout.readline()
    print output,

Wenn es lszu schnell endet, endet die while-Schleife möglicherweise, bevor Sie alle Daten gelesen haben.

Sie können den Rest in stdout folgendermaßen abfangen:

output = proc.communicate()[0]
print output,

1
Fällt dieses Schema dem Pufferblockierungsproblem zum Opfer, auf das sich das Python-Dokument bezieht?
Heinrich Schmetterling

@ Heinrich, das Problem der Pufferblockierung verstehe ich nicht gut. Ich glaube (nur durch Googeln), dass dieses Problem nur auftritt, wenn Sie nicht von stdout (und stderr?) Innerhalb der while-Schleife lesen. Ich denke, der obige Code ist in Ordnung, aber ich kann nicht sicher sagen.
Unutbu

1
Dies leidet tatsächlich unter einem Blockierungsproblem. Vor ein paar Jahren hatte ich kein Ende der Probleme, bei denen readline blockieren würde, bis es eine neue Zeile bekam, selbst wenn der Prozess beendet worden wäre. Ich erinnere mich nicht an die Lösung, aber ich denke, sie hatte etwas damit zu tun, die Lesevorgänge in einem Arbeitsthread durchzuführen und nur eine Schleife while proc.poll() is None: time.sleep(0)zu erstellen oder etwas in diesem Sinne. Grundsätzlich müssen Sie entweder sicherstellen, dass die Ausgabe-Newline das letzte ist, was der Prozess ausführt (weil Sie dem Interpreter keine Zeit zum erneuten Schleifen geben können), oder Sie müssen etwas "Phantasievolles" tun.
Dash-Tom-Bang

@ Heinrich: Alex Martelli schreibt darüber, wie man den Deadlock hier vermeidet: stackoverflow.com/questions/1445627/…
unutbu

6
Die Pufferblockierung ist einfacher als es manchmal klingt: Elternblöcke warten darauf, dass das Kind beendet wird + Kinderblöcke warten darauf, dass Eltern lesen und etwas Speicherplatz in der Kommunikationsleitung freigeben, der voll ist = Deadlock. So einfach ist das. Je kleiner das Rohr, desto wahrscheinlicher ist es.
MarcH

160

So erhalten Sie die Ausgabe des Unterprozesses zeilenweise, sobald der Unterprozess seinen Standardpuffer geleert hat:

#!/usr/bin/env python2
from subprocess import Popen, PIPE

p = Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1)
with p.stdout:
    for line in iter(p.stdout.readline, b''):
        print line,
p.wait() # wait for the subprocess to exit

iter()wird verwendet, um Zeilen zu lesen, sobald sie geschrieben wurden, um den Read-Ahead-Fehler in Python 2 zu umgehen .

Wenn stdout des Unterprozesses im nicht interaktiven Modus eine Blockpufferung anstelle einer Zeilenpufferung verwendet (was zu einer Verzögerung der Ausgabe führt, bis der Puffer des Kindes voll ist oder vom Kind explizit geleert wird), können Sie versuchen, eine ungepufferte Ausgabe mit zu erzwingen pexpect, ptyModule oder unbuffer, stdbuf, scriptDienstprogramme , siehe F: Warum nicht einfach ein Rohr verwenden (popen ())?


Hier ist Python 3-Code:

#!/usr/bin/env python3
from subprocess import Popen, PIPE

with Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1,
           universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='')

Hinweis: Im Gegensatz zu Python 2, das die Bytestrings des Unterprozesses unverändert ausgibt. Python 3 verwendet den Textmodus (die Ausgabe von cmd wird mithilfe der locale.getpreferredencoding(False)Codierung dekodiert ).


Was bedeutet das b ''?
Aaron

4
b''ist ein bytesLiteral in Python 2.7 und Python 3.
jfs

2
@JinghaoShi: bufsize=1Kann einen Unterschied machen, wenn Sie auch in den Unterprozess schreiben (verwenden p.stdin), z. B. kann es helfen, einen Deadlock zu vermeiden, während Sie einen interaktiven ( pexpectähnlichen) Austausch durchführen - vorausgesetzt, es gibt keine Pufferprobleme im untergeordneten Prozess selbst. Wenn Sie dann nur lesen, wie gesagt, liegt der Unterschied nur in der Leistung: Wenn dies nicht der Fall ist, können Sie dann ein minimales vollständiges Codebeispiel bereitstellen, das dies zeigt?
JFS

1
@ealeon: ja. Es erfordert Techniken , die können lesen stdout / stderr gleichzeitig , wenn Sie stderr in stdout fusionieren (indem stderr=subprocess.STDOUTzu Popen()). Siehe auch dort verknüpfte Threading- oder Asyncio-Lösungen .
JFS

2
@saulspatz Wenn stdout=PIPEdie Ausgabe nicht erfasst wird (Sie sehen sie immer noch auf dem Bildschirm), druckt Ihr Programm möglicherweise stattdessen auf stderr oder direkt auf dem Terminal. Um stdout & stderr zusammenzuführen, übergeben Sie stderr=subprocess.STDOUT(siehe meinen vorherigen Kommentar). Um die direkt auf Ihrem tty gedruckte Ausgabe zu erfassen, können Sie pexpect, pty-Lösungen verwenden. . Hier ist ein komplexeres Codebeispiel .
JFS

6

Ich glaube, der einfachste Weg, die Ausgabe eines Prozesses auf Streaming-Weise zu sammeln, ist folgender:

import sys
from subprocess import *
proc = Popen('ls', shell=True, stdout=PIPE)
while True:
    data = proc.stdout.readline()   # Alternatively proc.stdout.read(1024)
    if len(data) == 0:
        break
    sys.stdout.write(data)   # sys.stdout.buffer.write(data) on Python 3.x

Die Funktion readline()oder read()sollte nur eine leere Zeichenfolge in EOF zurückgeben, nachdem der Prozess beendet wurde. Andernfalls wird sie blockiert, wenn nichts zu lesen ist ( readline()einschließlich der neuen Zeile. Bei leeren Zeilen wird "\ n" zurückgegeben). Dies vermeidet die Notwendigkeit eines umständlichen letzten communicate()Aufrufs nach der Schleife.

Bei Dateien mit sehr langen Zeilen ist es read()möglicherweise vorzuziehen, die maximale Speichernutzung zu reduzieren. Die an sie übergebene Anzahl ist willkürlich. Wenn Sie sie jedoch ausschließen, wird die gesamte Pipe-Ausgabe auf einmal gelesen, was wahrscheinlich nicht wünschenswert ist.


4
data = proc.stdout.read()blockiert, bis alle Daten gelesen sind. Sie könnten es damit verwechseln, os.read(fd, maxsize)dass es früher zurückkehren kann (sobald Daten verfügbar sind).
JFS

Du hast recht, ich habe mich geirrt. Wenn jedoch eine angemessene Anzahl von Bytes als Argument übergeben wird, read()funktioniert dies einwandfrei und readline()funktioniert ebenfalls einwandfrei, solange die maximale Zeilenlänge angemessen ist. Meine Antwort wurde entsprechend aktualisiert.
D Coetzee


3

Wenn Sie einfach versuchen, die Ausgabe in Echtzeit weiterzuleiten, ist es schwierig, einfacher zu werden:

import subprocess

# This will raise a CalledProcessError if the program return a nonzero code.
# You can use call() instead if you don't care about that case.
subprocess.check_call(['ls', '-l'])

Siehe die Dokumente für subprocess.check_call () .

Wenn Sie die Ausgabe verarbeiten müssen, führen Sie eine Schleife durch. Aber wenn Sie dies nicht tun, halten Sie es einfach.

Bearbeiten: JF Sebastian weist darauf hin, dass die Standardeinstellungen für die Parameter stdout und stderr an sys.stdout und sys.stderr übergeben werden und dass dies fehlschlägt, wenn sys.stdout und sys.stderr ersetzt wurden (z. B. zum Erfassen der Ausgabe in) Tests).


Es wird nicht , wenn Arbeit sys.stdoutoder sys.stderrmit dateiähnliche Objekte ersetzt , die keinen wirklichen fileno haben (). Wenn sys.stdout, sys.stderrnicht ersetzt werden , dann ist es noch einfacher: subprocess.check_call(args).
JFS

Vielen Dank! Ich hatte die Unklarheiten beim Ersetzen von sys.stdout / stderr erkannt, aber irgendwie nie bemerkt, dass wenn Sie die Argumente weglassen, stdout und stderr an die richtigen Stellen übergeben werden. Ich mag call()es, check_call()wenn ich das nicht will CalledProcessError.
Nate

python -mthis: "Fehler sollten niemals stillschweigend weitergegeben werden. Es sei denn, dies wird ausdrücklich zum Schweigen gebracht." Deshalb ist der Beispielcode bevorzugen sollte check_call()über call().
JFS

Heh. Viele der Programme, die ich call()abschließe, geben unter fehlerfreien Bedingungen Fehlercodes ungleich Null zurück, weil sie schrecklich sind. In unserem Fall ist ein Fehlercode ungleich Null also kein Fehler.
Nate

Ja. Es gibt Programme wie grepdieses, die möglicherweise den Exit-Status ungleich Null zurückgeben, auch wenn kein Fehler vorliegt - dies sind Ausnahmen. Standardmäßig zeigt der Exit-Status Null Erfolg an.
JFS

1
myCommand="ls -l"
cmd=myCommand.split()
# "universal newline support" This will cause to interpret \n, \r\n and \r     equally, each as a newline.
p = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True)
while True:    
    print(p.stderr.readline().rstrip('\r\n'))

1
Es ist immer gut zu erklären, was Ihre Lösung tut, um die Menschen besser zu verstehen
DaFois

2
Sie sollten in Betracht ziehen, shlex.split(myCommand)anstelle von zu verwenden myCommand.split(). Leerzeichen werden auch in zitierten Argumenten berücksichtigt.
UtahJarhead
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.