Issues intercepting subprocess output in real time Issues intercepting subprocess output in real time tkinter tkinter

Issues intercepting subprocess output in real time


The problem here is that process.stdout.readline() will block until a full line is available. This means the condition line == '' will never be met until the process exits. You have two options around this.

First you can set stdout to non-blocking and manage a buffer yourself. It would look something like this. EDIT: As Terry Jan Reedy pointed out this is a Unix only solution. The second alternative should be preferred.

import fcntl...    def startProcess(self):        self.process = subprocess.Popen(['./subtest.sh'],            stdout=subprocess.PIPE,            stdin=subprocess.PIPE,            stderr=subprocess.PIPE,            bufsize=0) # prevent any unnecessary buffering        # set stdout to non-blocking        fd = self.process.stdout.fileno()        fl = fcntl.fcntl(fd, fcntl.F_GETFL)        fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)        # schedule updatelines        self.after(100, self.updateLines)    def updateLines(self):        # read stdout as much as we can        line = ''        while True:            buff = self.process.stdout.read(1024)            if buff:                buff += line.decode()            else:                break        self.console.config(state=tkinter.NORMAL)        self.console.insert(tkinter.END, line)        self.console.config(state=tkinter.DISABLED)        # schedule callback        if self.process.poll() is None:            self.after(100, self.updateLines)

The second alternative is to have a separate thread read the lines into a queue. Then have updatelines pop from the queue. It would look something like this

from threading import Threadfrom queue import Queue, Emptydef readlines(process, queue):    while process.poll() is None:        queue.put(process.stdout.readline())...    def startProcess(self):        self.process = subprocess.Popen(['./subtest.sh'],            stdout=subprocess.PIPE,            stdin=subprocess.PIPE,            stderr=subprocess.PIPE)        self.queue = Queue()        self.thread = Thread(target=readlines, args=(self.process, self.queue))        self.thread.start()        self.after(100, self.updateLines)    def updateLines(self):        try:            line = self.queue.get(False) # False for non-blocking, raises Empty if empty            self.console.config(state=tkinter.NORMAL)            self.console.insert(tkinter.END, line)            self.console.config(state=tkinter.DISABLED)        except Empty:            pass        if self.process.poll() is None:            self.after(100, self.updateLines)

The threading route is probably safer. I'm not positive that setting stdout to non-blocking will work on all platforms.