Mein Problem ist etwas anders, da ich sowohl stdout als auch stderr aus einem laufenden Prozess sammeln wollte, aber letztendlich dasselbe, da ich die Ausgabe in einem Widget so rendern wollte, wie sie generiert wurde.
Ich wollte nicht auf viele der vorgeschlagenen Problemumgehungen mit Warteschlangen oder zusätzlichen Threads zurückgreifen, da diese nicht erforderlich sein sollten, um eine so allgemeine Aufgabe wie das Ausführen eines anderen Skripts und das Sammeln seiner Ausgabe auszuführen.
Nachdem ich die vorgeschlagenen Lösungen und Python-Dokumente gelesen hatte, löste ich mein Problem mit der folgenden Implementierung. Ja, es funktioniert nur für POSIX, da ich den select
Funktionsaufruf verwende.
Ich bin damit einverstanden, dass die Dokumente verwirrend sind und die Implementierung für eine solche häufig verwendete Skriptaufgabe umständlich ist. Ich glaube, dass ältere Versionen von Python unterschiedliche Standardeinstellungen Popen
und Erklärungen haben, was zu großer Verwirrung geführt hat. Dies scheint sowohl für Python 2.7.12 als auch für 3.5.2 gut zu funktionieren.
Der Schlüssel bestand darin, die bufsize=1
Zeilenpufferung festzulegen und dann universal_newlines=True
als Textdatei anstelle einer Binärdatei zu verarbeiten, die bei der Einstellung als Standard zu gelten scheint bufsize=1
.
class workerThread(QThread):
def __init__(self, cmd):
QThread.__init__(self)
self.cmd = cmd
self.result = None ## return code
self.error = None ## flag indicates an error
self.errorstr = "" ## info message about the error
def __del__(self):
self.wait()
DEBUG("Thread removed")
def run(self):
cmd_list = self.cmd.split(" ")
try:
cmd = subprocess.Popen(cmd_list, bufsize=1, stdin=None
, universal_newlines=True
, stderr=subprocess.PIPE
, stdout=subprocess.PIPE)
except OSError:
self.error = 1
self.errorstr = "Failed to execute " + self.cmd
ERROR(self.errorstr)
finally:
VERBOSE("task started...")
import select
while True:
try:
r,w,x = select.select([cmd.stdout, cmd.stderr],[],[])
if cmd.stderr in r:
line = cmd.stderr.readline()
if line != "":
line = line.strip()
self.emit(SIGNAL("update_error(QString)"), line)
if cmd.stdout in r:
line = cmd.stdout.readline()
if line == "":
break
line = line.strip()
self.emit(SIGNAL("update_output(QString)"), line)
except IOError:
pass
cmd.wait()
self.result = cmd.returncode
if self.result < 0:
self.error = 1
self.errorstr = "Task terminated by signal " + str(self.result)
ERROR(self.errorstr)
return
if self.result:
self.error = 1
self.errorstr = "exit code " + str(self.result)
ERROR(self.errorstr)
return
return
ERROR, DEBUG und VERBOSE sind einfach Makros, die die Ausgabe an das Terminal drucken.
Diese Lösung ist meiner Meinung nach zu 99,99% effektiv, da sie weiterhin die Blockierungsfunktion readline
verwendet. Wir gehen daher davon aus, dass der Unterprozess gut ist und vollständige Zeilen ausgibt.
Ich freue mich über Feedback zur Verbesserung der Lösung, da ich Python noch nicht kenne.