webentwicklung-frage-antwort-db.com.de

liest Unterprozess stdout Zeile für Zeile

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 etwas davon zeigen. Ich dachte, das folgende würde funktionieren, aber die Ausgabe wird in meiner Anwendung nicht angezeigt, bis 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 so druckt, wie sie vom Unterprozess empfangen wird. Sorta wie tee , jedoch mit Python-Code.

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


Update:

Wenn fake_utility.py eine sys.stdout.flush() hinzugefügt wird, weist der Code in Python 3.1 das gewünschte Verhalten auf. Ich verwende Python 2.6. Man könnte meinen, dass die Verwendung von 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()
195
deft_code

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 ich sie durchläuft. Die Lösung ist, stattdessen readline() zu verwenden:

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

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

Hinweis: Gemäß der Dokumentation sollte die Lösung mit einem Iterator der Verwendung von readline() entsprechen, mit Ausnahme des Read-Ahead-Puffers. Die vorgeschlagene Änderung führte jedoch (oder genau aus diesem Grund) zu unterschiedlichen Ergebnissen (Python 2.5) unter Windows XP).

151
Rômulo Ceccon

Etwas spät zur Party, war aber überrascht, nicht zu sehen, was ich für die einfachste Lösung halte:

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
30
jbg

Wenn Sie den Iterator aussortiert haben, könnte die Pufferung jetzt zu Ihrem Problem werden. Sie könnten dem Python im Unterprozess mitteilen, dass er seine Ausgabe nicht zwischenspeichern soll.

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 beim Aufruf von Python aus Python heraus benötigt.

16
Steve Carter

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

bufsize=1, universal_newlines=True

Dann können Sie wie in Ihrem Beispiel iterieren. (Mit Python 3.5 getestet)

11
user1747134

Die folgende Modifikation der Antwort von Rômulo funktioniert für mich auf 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
1
mdh

Eine Funktion, mit der sowohl stdout als auch stderr in Echtzeit zeilenweise durchlaufen werden können

Falls Sie den Ausgabestream für stdout und stderr gleichzeitig abrufen müssen, können Sie die folgende Funktion verwenden.

Die Funktion verwendet Queues, 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()
                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
0
Rotareti

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()
0
Aiven

Ich habe es mit python3 ausprobiert und es hat funktioniert, source

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()
0
shakram02