Mój problem jest nieco inny, ponieważ chciałem zebrać zarówno stdout, jak i stderr z uruchomionego procesu, ale ostatecznie taki sam, ponieważ chciałem renderować dane wyjściowe w widgecie po jego wygenerowaniu.
Nie chciałem uciekać się do wielu proponowanych obejść przy użyciu kolejek lub dodatkowych wątków, ponieważ nie powinny one być konieczne do wykonania tak typowego zadania, jak uruchomienie innego skryptu i zebranie jego danych wyjściowych.
Po przeczytaniu proponowanych rozwiązań i dokumentów w języku Python rozwiązałem problem z implementacją poniżej. Tak, działa tylko dla POSIX, ponieważ używam select
wywołania funkcji.
Zgadzam się, że dokumenty są mylące, a implementacja jest niezręczna w przypadku tak powszechnego zadania skryptowego. Uważam, że starsze wersje Pythona mają różne domyślne ustawienia Popen
i różne wyjaśnienia, co spowodowało wiele zamieszania. Wydaje się, że działa to dobrze zarówno w Pythonie 2.7.12, jak i 3.5.2.
Kluczem było ustawienie bufsize=1
buforowania linii, a następnie universal_newlines=True
przetwarzanie jako plik tekstowy zamiast pliku binarnego, który wydaje się być domyślny podczas ustawiania 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 i VERBOSE to po prostu makra, które wypisują dane wyjściowe na terminal.
To rozwiązanie jest skuteczne IMHO 99,99%, ponieważ nadal korzysta z readline
funkcji blokowania , więc zakładamy, że podproces jest przyjemny i generuje pełne linie.
Czekam na opinie w celu ulepszenia rozwiązania, ponieważ wciąż jestem nowy w Pythonie.