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()
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).
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
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.
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)
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
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
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()
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()