Meu problema é um pouco diferente, pois eu queria coletar stdout e stderr de um processo em execução, mas, em última análise, o mesmo, pois eu queria renderizar a saída em um widget conforme ele fosse gerado.
Eu não queria recorrer a muitas das soluções alternativas propostas usando Filas ou Threads adicionais, pois elas não seriam necessárias para executar uma tarefa tão comum como executar outro script e coletar sua saída.
Depois de ler as soluções propostas e os documentos python, resolvi meu problema com a implementação abaixo. Sim, só funciona para POSIX, pois estou usando a select
chamada de função.
Concordo que os documentos são confusos e a implementação é estranha para uma tarefa de script tão comum. Acredito que versões mais antigas do python têm diferentes padrões Popen
e explicações diferentes, o que gerou muita confusão. Isso parece funcionar bem para o Python 2.7.12 e 3.5.2.
A chave era definir o bufsize=1
buffer de linha e depois universal_newlines=True
processar como um arquivo de texto em vez de um binário que parece se tornar o padrão na configuração 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
ERRO, DEBUG e VERBOSE são simplesmente macros que imprimem a saída no terminal.
Esta solução é IMHO 99,99% eficaz, pois ainda usa a readline
função de bloqueio , por isso assumimos que o subprocesso é bom e gera linhas completas.
Congratulo-me com o feedback para melhorar a solução, pois ainda sou novo no Python.