47

I'd like to use the subprocess module in the following way:

  1. create a new process that potentially takes a long time to execute.
  2. capture stdout (or stderr, or potentially both, either together or separately)
  3. Process data from the subprocess as it comes in, perhaps firing events on every line received (in wxPython say) or simply printing them out for now.

I've created processes with Popen, but if I use communicate() the data comes at me all at once, once the process has terminated.

If I create a separate thread that does a blocking readline() of myprocess.stdout (using stdout = subprocess.PIPE) I don't get any lines with this method either, until the process terminates. (no matter what I set as bufsize)

Is there a way to deal with this that isn't horrendous, and works well on multiple platforms?

Robert
  • 1,286
  • 1
  • 17
  • 37
Ryan
  • 4,179
  • 6
  • 30
  • 31
  • 2
    myprocess.stdout.readline() should work. Can you show us your code? – Ayman Hourieh May 17 '09 at 15:28
  • Unbuffered reads from popen_obj.stdout() should indeed work -- but if you don't mind being limited to platforms with PTY support, your application might be suitable for the Pexpect library. – Charles Duffy May 17 '09 at 15:46
  • 1
    This is a great question and it still seems to be unanswered, at least for the "works well on multiple platforms" requirement. – Steven T. Snyder Mar 22 '11 at 00:01
  • related: [Getting realtime output using subprocess](http://stackoverflow.com/questions/803265/getting-realtime-output-using-subprocess) – jfs Oct 16 '14 at 20:15
  • here's how [to capture and display in "real-time" time both stdout and stderr separately from a child process line by line in a portable manner in a single thread in Python 3 (`asyncio`)](http://stackoverflow.com/a/25960956/4279) and here's [multithreaded solution (`teed_call()`)](http://stackoverflow.com/q/25750468/4279). – jfs Oct 16 '14 at 20:21
  • here's [how to read subprocess' output in a GUI (threads/no threads solutions)](http://stackoverflow.com/questions/22627148/tkinter-subprocess-locking-gui-and-not-returning-stdout-to-text#comment34460372_22627148) – jfs Oct 16 '14 at 20:26

10 Answers10

8

Update with code that appears not to work (on windows anyway)

class ThreadWorker(threading.Thread):
    def __init__(self, callable, *args, **kwargs):
        super(ThreadWorker, self).__init__()
        self.callable = callable
        self.args = args
        self.kwargs = kwargs
        self.setDaemon(True)

    def run(self):
        try:
            self.callable(*self.args, **self.kwargs)
        except wx.PyDeadObjectError:
            pass
        except Exception, e:
            print e



if __name__ == "__main__":
    import os
    from subprocess import Popen, PIPE

    def worker(pipe):
        while True:
            line = pipe.readline()
            if line == '': break
            else: print line

    proc = Popen("python subprocess_test.py", shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)

    stdout_worker = ThreadWorker(worker, proc.stdout)
    stderr_worker = ThreadWorker(worker, proc.stderr)
    stdout_worker.start()
    stderr_worker.start()
    while True: pass
Ryan
  • 4,179
  • 6
  • 30
  • 31
  • This is clearly the best answer. Thanks for showing me this 'pipe' type in Python! – Mapad Nov 26 '09 at 10:11
  • This is a great answer, and works for me. The key is that the reads can block without any problem due to the threads. – Paul Biggar Dec 04 '10 at 21:04
  • 1
    `while True: time.sleep(1)` is better than a busy wait loop consuming all your CPU. – ReneSac Feb 15 '13 at 13:45
  • 1
    How does it fail on Windows? – jfs Dec 21 '13 at 05:35
  • call `proc.stdin.close()` if you use `stdin=PIPE`. Use `for t in [stdout_worker, stderr_workerr]: t.join()` instead of `while True:pass`. You could use `iter(pipe.readline, b'')` instead of the while loop in `worker()`. – jfs Dec 21 '13 at 05:38
7

stdout will be buffered - so you won't get anything till that buffer is filled, or the subprocess exits.

You can try flushing stdout from the sub-process, or using stderr, or changing stdout on non-buffered mode.

Douglas Leeder
  • 52,368
  • 9
  • 94
  • 137
  • 2
    Shouldn't it be unbuffered by default? At least with bufsize=0 ? – Albert May 14 '10 at 22:32
  • 2
    @Albert: the buffer is *inside* subprocess e.g., `stdio` buffer. Nothing outside the child process sees that data until it flushes its stdout buffer. Here're some workarounds for [the buffering issue](http://stackoverflow.com/a/20509641/4279) – jfs Dec 21 '13 at 05:32
2

Here's what worked for me:

cmd = ["./tester_script.bash"]
p = subprocess.Popen( cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
while p.poll() is None:
    out = p.stdout.readline()
    do_something_with( out, err )

In your case you could try to pass a reference to the sub-process to your Worker Thread, and do the polling inside the thread. I don't know how it will behave when two threads poll (and interact with) the same subprocess, but it may work.

Also note thate the while p.poll() is None: is intended as is. Do not replace it with while not p.poll() as in python 0 (the returncode for successful termination) is also considered False.

exhuma
  • 20,071
  • 12
  • 90
  • 123
  • I'm not sure if there's something I'm missing here but it seems like your code is actually blocking: that "while" loop means that whatever the output of that function should be, it won't be returned until p.poll() is None. – pgcd Jul 28 '15 at 08:39
  • Yes. This is blocking. I guess that back when I answered this, I did not notice the threading part of the question. Even so, my answer here is not quite "up-to-date". The main problem is that output is buffered (as mentioned elsewhere), and I don't mention this here. I will leave it here for posterity, but other submitted answers are better. – exhuma Jul 28 '15 at 09:07
2

It sounds like the issue might be the use of buffered output by the subprocess - if a relatively small amount of output is created, it could be buffered until the subprocess exits. Some background can be found here:

Lance Richardson
  • 4,610
  • 23
  • 30
1

This seems to be a well-known Python limitation, see PEP 3145 and maybe others.

MarcH
  • 18,738
  • 1
  • 30
  • 25
1

I've been running into this problem as well. The problem occurs because you are trying to read stderr as well. If there are no errors, then trying to read from stderr would block.

On Windows, there is no easy way to poll() file descriptors (only Winsock sockets).

So a solution is not to try and read from stderr.

khcheng
  • 11
  • 1
1

Read one character at a time: http://blog.thelinuxkid.com/2013/06/get-python-subprocess-output-without.html

import contextlib
import subprocess

# Unix, Windows and old Macintosh end-of-line
newlines = ['\n', '\r\n', '\r']
def unbuffered(proc, stream='stdout'):
    stream = getattr(proc, stream)
    with contextlib.closing(stream):
        while True:
            out = []
            last = stream.read(1)
            # Don't loop forever
            if last == '' and proc.poll() is not None:
                break
            while last not in newlines:
                # Don't loop forever
                if last == '' and proc.poll() is not None:
                    break
                out.append(last)
                last = stream.read(1)
            out = ''.join(out)
            yield out

def example():
    cmd = ['ls', '-l', '/']
    proc = subprocess.Popen(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        # Make all end-of-lines '\n'
        universal_newlines=True,
    )
    for line in unbuffered(proc):
        print line

example()
Andres Restrepo
  • 389
  • 5
  • 6
  • it seems to be a duplicate answer. See [my previous comment](http://stackoverflow.com/questions/803265/getting-realtime-output-using-subprocess/15288921#comment41473849_15288921) – jfs Oct 16 '14 at 20:08
1

Using pexpect [http://www.noah.org/wiki/Pexpect] with non-blocking readlines will resolve this problem. It stems from the fact that pipes are buffered, and so your app's output is getting buffered by the pipe, therefore you can't get to that output until the buffer fills or the process dies.

Gabe
  • 1,028
  • 1
  • 10
  • 11
1

Using subprocess.Popen, I can run the .exe of one of my C# projects and redirect the output to my Python file. I am able now to print() all the information being output to the C# console (using Console.WriteLine()) to the Python console.

Python code:

from subprocess import Popen, PIPE, STDOUT

p = Popen('ConsoleDataImporter.exe', stdout = PIPE, stderr = STDOUT, shell = True)

while True:
    line = p.stdout.readline()
    print(line)
    if not line:
        break

This gets the console output of my .NET project line by line as it is created and breaks out of the enclosing while loop upon the project's termination. I'd imagine this would work for two python files as well.

0

I've used the pexpect module for this, it seems to work ok. http://sourceforge.net/projects/pexpect/