Lesen Sie den Teilprozess stdout Zeile für Zeile

235

Mein Python-Skript verwendet einen Unterprozess, um ein Linux-Dienstprogramm aufzurufen, das sehr laut ist. Ich möchte die gesamte Ausgabe in einer Protokolldatei speichern und dem Benutzer einen Teil davon anzeigen. Ich dachte, das Folgende würde funktionieren, aber die Ausgabe wird in meiner Anwendung erst angezeigt, wenn das Dienstprogramm eine erhebliche Menge an Ausgabe erzeugt hat.

#fake_utility.py, just generates lots of output over time
import time
i = 0
while True:
   print hex(i)*512
   i += 1
   time.sleep(0.5)

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
for line in proc.stdout:
   #the real code does filtering here
   print "test:", line.rstrip()

Das Verhalten, das ich wirklich möchte, besteht darin, dass das Filterskript jede Zeile druckt, wenn sie vom Unterprozess empfangen wird. Sorta mag was teemacht aber mit Python Code.

Was vermisse ich? Ist das überhaupt möglich?


Aktualisieren:

Wenn a sys.stdout.flush()zu fake_utility.py hinzugefügt wird, hat der Code das gewünschte Verhalten in Python 3.1. Ich benutze Python 2.6. Sie würden denken, dass die Verwendung proc.stdout.xreadlines()genauso funktioniert wie py3k, aber das funktioniert nicht.


Update 2:

Hier ist der minimale Arbeitscode.

#fake_utility.py, just generates lots of output over time
import sys, time
for i in range(10):
   print i
   sys.stdout.flush()
   time.sleep(0.5)

#display out put line by line
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
#works in python 3.0+
#for line in proc.stdout:
for line in iter(proc.stdout.readline,''):
   print line.rstrip()
deft_code
quelle
4
Sie können print line,anstelle von print line.rstrip()(Hinweis: Komma am Ende) verwenden.
JFS
2
Update 2 besagt, dass es mit Python 3.0+ funktioniert, aber die alte print-Anweisung verwendet, sodass es nicht mit Python 3.0+ funktioniert.
Rooky
Keine der hier aufgeführten Antworten hat bei mir funktioniert, aber stackoverflow.com/questions/5411780/… hat es geschafft!
Boxed

Antworten:

179

Es ist lange her, seit ich das letzte Mal mit Python gearbeitet habe, aber ich denke, das Problem liegt in der Anweisung for line in proc.stdout, die die gesamte Eingabe liest, bevor sie darüber iteriert. Die Lösung besteht darin, readline()stattdessen Folgendes zu verwenden :

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
while True:
  line = proc.stdout.readline()
  if not line:
    break
  #the real code does filtering here
  print "test:", line.rstrip()

Natürlich müssen Sie sich noch mit der Pufferung des Unterprozesses befassen.

Hinweis: Gemäß der Dokumentation sollte die Lösung mit einem Iterator mit readline()Ausnahme des Vorauslesepuffers der Verwendung entsprechen, aber (oder genau aus diesem Grund) die vorgeschlagene Änderung führte für mich zu unterschiedlichen Ergebnissen (Python 2.5 unter Windows XP).

Rômulo Ceccon
quelle
11
für file.readline()vs. for line in filesiehe bugs.python.org/issue3907 (kurz: es funktioniert unter Python3; Verwendung io.open()unter Python 2.6+)
jfs
5
Der pythonischere Test für einen EOF gemäß den "Programmierempfehlungen" in PEP 8 ( python.org/dev/peps/pep-0008 ) wäre "wenn nicht Zeile:".
Jason Mock
14
@naxa: für Rohre : for line in iter(proc.stdout.readline, ''):.
JFS
3
@ Jan-PhilipGehrcke: ja. 1. Sie könnten es for line in proc.stdoutunter Python 3 verwenden (es gibt keinen Read-Ahead-Fehler). 2. Unter '' != b''Python 3 - Kopieren Sie den Code nicht blind und fügen Sie ihn ein - überlegen Sie, was er tut und wie er funktioniert.
JFS
2
@JFSebastian: Sicher, die iter(f.readline, b'')Lösung ist ziemlich offensichtlich (und funktioniert auch auf Python 2, wenn jemand interessiert ist). In meinem Kommentar ging es nicht darum, Ihre Lösung zu beschuldigen (Entschuldigung, wenn es so aussah, das habe ich jetzt auch gelesen!), Sondern darum, das Ausmaß der Symptome zu beschreiben, die in diesem Fall ziemlich schwerwiegend sind (die meisten Py2 / 3 Probleme führen zu Ausnahmen, während sich hier eine gut erzogene Schleife in endlos geändert hat und die Speicherbereinigung Probleme hat, die Flut neu erstellter Objekte zu bekämpfen, was zu Schwankungen der Speichernutzung mit langer Periode und großer Amplitude führt.
Dr. Jan-Philip Gehrcke
45

Etwas spät zur Party, war aber überrascht, nicht zu sehen, was meiner Meinung nach die einfachste Lösung hier ist:

import io
import subprocess

proc = subprocess.Popen(["prog", "arg"], stdout=subprocess.PIPE)
for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):  # or another encoding
    # do something with line

(Dies erfordert Python 3.)

jbg
quelle
25
Ich würde diese Antwort gerne verwenden, aber ich AttributeError: 'file' object has no attribute 'readable' bekomme : py2.7
Dan Garthwaite
3
Arbeitet mit Python 3
Matanster
Offensichtlich ist dieser Code aus mehreren Gründen nicht gültig. Py3 / py3-Kompatibilität und echtes Risiko, ValueError zu erhalten: E / A-Operation für geschlossene Datei
13.
3
@sorin keines dieser Dinge macht es "ungültig". Wenn Sie eine Bibliothek schreiben, die Python 2 noch unterstützen muss, verwenden Sie diesen Code nicht. Aber viele Menschen haben den Luxus, Software verwenden zu können, die vor mehr als einem Jahrzehnt veröffentlicht wurde. Wenn Sie versuchen, eine geschlossene Datei zu lesen, wird diese Ausnahme unabhängig davon angezeigt, ob Sie sie verwenden TextIOWrapperoder nicht. Sie können die Ausnahme einfach behandeln.
jbg
1
Sie sind vielleicht zu spät zur Party, aber Sie antworten, dass die aktuelle Version von Python, ty
Dusan Gligoric
20

Wenn Sie den Iterator aussortiert haben, könnte die Pufferung jetzt Ihr Problem sein. Sie können die Python im Unterprozess anweisen, ihre Ausgabe nicht zu puffern.

proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)

wird

proc = subprocess.Popen(['python','-u', 'fake_utility.py'],stdout=subprocess.PIPE)

Ich habe dies benötigt, wenn ich Python aus Python heraus aufgerufen habe.

Steve Carter
quelle
14

Sie möchten diese zusätzlichen Parameter übergeben an subprocess.Popen:

bufsize=1, universal_newlines=True

Dann können Sie wie in Ihrem Beispiel iterieren. (Getestet mit Python 3.5)

user1747134
quelle
2
@nicoulaj Es sollte funktionieren, wenn das subprocess32-Paket verwendet wird.
Quantum7
4

Eine Funktion , die Iterieren über beide ermöglicht stdoutund stderrgleichzeitig, in Echtzeit, zeilen

Für den Fall , müssen Sie den Ausgabestrom für beide bekommen stdoutund stderrzur gleichen Zeit können Sie die folgende Funktion verwenden.

Die Funktion verwendet Warteschlangen, um beide Popen-Pipes zu einem einzigen Iterator zusammenzuführen.

Hier erstellen wir die Funktion read_popen_pipes():

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()
            except Empty:
                pass
            try:
                err_line = q_stderr.get_nowait()
            except Empty:
                pass

            yield (out_line, err_line)

read_popen_pipes() in Benutzung:

import subprocess as sp


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

        # Do stuff with each line, e.g.:
        print(out_line, end='')
        print(err_line, end='')

    return p.poll() # return status-code
Rotareti
quelle
2

Sie können auch Zeilen ohne Schleife lesen. Funktioniert in Python3.6.

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
list_of_byte_strings = process.stdout.readlines()
aiven
quelle
1
Oder um in Strings umzuwandeln:list_of_strings = [x.decode('utf-8').rstrip('\n') for x in iter(process.stdout.readlines())]
ndtreviv
1

Ich habe es mit Python3 versucht und es hat funktioniert, Quelle

def output_reader(proc):
    for line in iter(proc.stdout.readline, b''):
        print('got line: {0}'.format(line.decode('utf-8')), end='')


def main():
    proc = subprocess.Popen(['python', 'fake_utility.py'],
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT)

    t = threading.Thread(target=output_reader, args=(proc,))
    t.start()

    try:
        time.sleep(0.2)
        import time
        i = 0

        while True:
        print (hex(i)*512)
        i += 1
        time.sleep(0.5)
    finally:
        proc.terminate()
        try:
            proc.wait(timeout=0.2)
            print('== subprocess exited with rc =', proc.returncode)
        except subprocess.TimeoutExpired:
            print('subprocess did not terminate in time')
    t.join()
shakram02
quelle
1

Die folgende Änderung von Rômulos Antwort funktioniert für mich unter Python 2 und 3 (2.7.12 und 3.6.1):

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
while True:
  line = process.stdout.readline()
  if line != '':
    os.write(1, line)
  else:
    break
mdh
quelle
0

Keine Ahnung, wann dies dem Unterprozessmodul hinzugefügt wurde, aber mit Python 3 sollten Sie in Ordnung sein mit proc.stdout.splitlines():

for line in proc.stdout.splitlines():
   print "stdout:", line
StefanQ
quelle