312

I'm using a python script as a driver for a hydrodynamics code. When it comes time to run the simulation, I use subprocess.Popen to run the code, collect the output from stdout and stderr into a subprocess.PIPE --- then I can print (and save to a log-file) the output information, and check for any errors. The problem is, I have no idea how the code is progressing. If I run it directly from the command line, it gives me output about what iteration its at, what time, what the next time-step is, etc.

Is there a way to both store the output (for logging and error checking), and also produce a live-streaming output?

The relevant section of my code:

ret_val = subprocess.Popen( run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True )
output, errors = ret_val.communicate()
log_file.write(output)
print output
if( ret_val.returncode ):
    print "RUN failed\n\n%s\n\n" % (errors)
    success = False

if( errors ): log_file.write("\n\n%s\n\n" % errors)

Originally I was piping the run_command through tee so that a copy went directly to the log-file, and the stream still output directly to the terminal -- but that way I can't store any errors (to my knowlege).


My temporary solution so far:

ret_val = subprocess.Popen( run_command, stdout=log_file, stderr=subprocess.PIPE, shell=True )
while not ret_val.poll():
    log_file.flush()

then, in another terminal, run tail -f log.txt (s.t. log_file = 'log.txt').

8
  • 1
    Maybe you can use Popen.poll as in a previous Stack Overflow question. Commented Aug 24, 2013 at 18:42
  • 1
    Some commands that show progress indication (e.g., git) do so only if their output is a "tty device" (tested via libc isatty()). In that case you may have to open a pseudo-tty.
    – torek
    Commented Aug 24, 2013 at 19:01
  • @torek what's a (pseudo-)tty? Commented Aug 24, 2013 at 19:02
  • 2
    Devices on Unix-like systems that allow a process to pretend to be a user on a serial port. This is how ssh (server side) works, for instance. See python pty library, and also pexpect.
    – torek
    Commented Aug 24, 2013 at 19:06
  • Re temporary solution: there's no need to call flush, and there is need to read from the stderr pipe if the subprocess produces much stderr output. There is not room enough in a comment field to explain this...
    – torek
    Commented Aug 24, 2013 at 19:14

28 Answers 28

247

TLDR for Python 3:

import subprocess
import sys

with open("test.log", "wb") as f:
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for c in iter(lambda: process.stdout.read(1), b""):
        sys.stdout.buffer.write(c)
        f.buffer.write(c)

You have two ways of doing this, either by creating an iterator from the read or readline functions and do:

import subprocess
import sys

# replace "w" with "wb" for Python 3
with open("test.log", "w") as f:
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    # replace "" with b'' for Python 3
    for c in iter(lambda: process.stdout.read(1), ""):
        sys.stdout.write(c)
        f.write(c)

or

import subprocess
import sys

# replace "w" with "wb" for Python 3
with open("test.log", "w") as f:
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    # replace "" with b"" for Python 3
    for line in iter(process.stdout.readline, ""):
        sys.stdout.write(line)
        f.write(line)

Or you can create a reader and a writer file. Pass the writer to the Popen and read from the reader

import io
import time
import subprocess
import sys

filename = "test.log"
with io.open(filename, "wb") as writer, io.open(filename, "rb", 1) as reader:
    process = subprocess.Popen(command, stdout=writer)
    while process.poll() is None:
        sys.stdout.write(reader.read())
        time.sleep(0.5)
    # Read the remaining
    sys.stdout.write(reader.read())

This way you will have the data written in the test.log as well as on the standard output.

The only advantage of the file approach is that your code doesn't block. So you can do whatever you want in the meantime and read whenever you want from the reader in a non-blocking way. When you use PIPE, read and readline functions will block until either one character is written to the pipe or a line is written to the pipe respectively.

17
  • 19
    With Python 3, you need iter(process.stdout.readline, b'') (i.e. the sentinel passed to iter needs to be a binary string, since b'' != ''. Commented Apr 12, 2015 at 17:58
  • 5
    For binary streams, do this:for line in iter(process.stdout.readline, b''): sys.stdout.buffer.write(line) Commented Jul 5, 2017 at 23:21
  • 11
    Adding to @JohnMellor 's answer, in Python 3 the following modifications were needed: process = subprocess.Popen(command, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) for line in iter(process.stdout.readline, b'') sys.stdout.write(line.decode(sys.stdout.encoding)) Commented Jan 16, 2018 at 9:21
  • 15
    but the output is not live, is it? in my experience, it just waits until the process finishes executing and only then prints to the console. Link -> stackoverflow.com/questions/30026045/…
    – denis631
    Commented Jun 5, 2019 at 11:58
  • 4
    I am gettingAttributeError: '_io.BufferedWriter' object has no attribute 'buffer', any idea what is going wrong?
    – Kvothe
    Commented Feb 24, 2021 at 14:28
125

Executive Summary (or "tl;dr" version): it's easy when there's at most one subprocess.PIPE, otherwise it's hard.

It may be time to explain a bit about how subprocess.Popen does its thing.

(Caveat: this is for Python 2.x, although 3.x is similar; and I'm quite fuzzy on the Windows variant. I understand the POSIX stuff much better.)

The Popen function needs to deal with zero-to-three I/O streams, somewhat simultaneously. These are denoted stdin, stdout, and stderr as usual.

You can provide:

  • None, indicating that you don't want to redirect the stream. It will inherit these as usual instead. Note that on POSIX systems, at least, this does not mean it will use Python's sys.stdout, just Python's actual stdout; see demo at end.
  • An int value. This is a "raw" file descriptor (in POSIX at least). (Side note: PIPE and STDOUT are actually ints internally, but are "impossible" descriptors, -1 and -2.)
  • A stream—really, any object with a fileno method. Popen will find the descriptor for that stream, using stream.fileno(), and then proceed as for an int value.
  • subprocess.PIPE, indicating that Python should create a pipe.
  • subprocess.STDOUT (for stderr only): tell Python to use the same descriptor as for stdout. This only makes sense if you provided a (non-None) value for stdout, and even then, it is only needed if you set stdout=subprocess.PIPE. (Otherwise you can just provide the same argument you provided for stdout, e.g., Popen(..., stdout=stream, stderr=stream).)

The easiest cases (no pipes)

If you redirect nothing (leave all three as the default None value or supply explicit None), Pipe has it quite easy. It just needs to spin off the subprocess and let it run. Or, if you redirect to a non-PIPE—an int or a stream's fileno()—it's still easy, as the OS does all the work. Python just needs to spin off the subprocess, connecting its stdin, stdout, and/or stderr to the provided file descriptors.

The still-easy case: one pipe

If you redirect only one stream, Pipe still has things pretty easy. Let's pick one stream at a time and watch.

Suppose you want to supply some stdin, but let stdout and stderr go un-redirected, or go to a file descriptor. As the parent process, your Python program simply needs to use write() to send data down the pipe. You can do this yourself, e.g.:

proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc

or you can pass the stdin data to proc.communicate(), which then does the stdin.write shown above. There is no output coming back so communicate() has only one other real job: it also closes the pipe for you. (If you don't call proc.communicate() you must call proc.stdin.close() to close the pipe, so that the subprocess knows there is no more data coming through.)

Suppose you want to capture stdout but leave stdin and stderr alone. Again, it's easy: just call proc.stdout.read() (or equivalent) until there is no more output. Since proc.stdout() is a normal Python I/O stream you can use all the normal constructs on it, like:

for line in proc.stdout:

or, again, you can use proc.communicate(), which simply does the read() for you.

If you want to capture only stderr, it works the same as with stdout.

There's one more trick before things get hard. Suppose you want to capture stdout, and also capture stderr but on the same pipe as stdout:

proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

In this case, subprocess "cheats"! Well, it has to do this, so it's not really cheating: it starts the subprocess with both its stdout and its stderr directed into the (single) pipe-descriptor that feeds back to its parent (Python) process. On the parent side, there's again only a single pipe-descriptor for reading the output. All the "stderr" output shows up in proc.stdout, and if you call proc.communicate(), the stderr result (second value in the tuple) will be None, not a string.

The hard cases: two or more pipes

The problems all come about when you want to use at least two pipes. In fact, the subprocess code itself has this bit:

def communicate(self, input=None):
    ...
    # Optimization: If we are only using one pipe, or no pipe at
    # all, using select() or threads is unnecessary.
    if [self.stdin, self.stdout, self.stderr].count(None) >= 2:

But, alas, here we've made at least two, and maybe three, different pipes, so the count(None) returns either 1 or 0. We must do things the hard way.

On Windows, this uses threading.Thread to accumulate results for self.stdout and self.stderr, and has the parent thread deliver self.stdin input data (and then close the pipe).

On POSIX, this uses poll if available, otherwise select, to accumulate output and deliver stdin input. All this runs in the (single) parent process/thread.

Threads or poll/select are needed here to avoid deadlock. Suppose, for instance, that we've redirected all three streams to three separate pipes. Suppose further that there's a small limit on how much data can be stuffed into to a pipe before the writing process is suspended, waiting for the reading process to "clean out" the pipe from the other end. Let's set that small limit to a single byte, just for illustration. (This is in fact how things work, except that the limit is much bigger than one byte.)

If the parent (Python) process tries to write several bytes—say, 'go\n'to proc.stdin, the first byte goes in and then the second causes the Python process to suspend, waiting for the subprocess to read the first byte, emptying the pipe.

Meanwhile, suppose the subprocess decides to print a friendly "Hello! Don't Panic!" greeting. The H goes into its stdout pipe, but the e causes it to suspend, waiting for its parent to read that H, emptying the stdout pipe.

Now we're stuck: the Python process is asleep, waiting to finish saying "go", and the subprocess is also asleep, waiting to finish saying "Hello! Don't Panic!".

The subprocess.Popen code avoids this problem with threading-or-select/poll. When bytes can go over the pipes, they go. When they can't, only a thread (not the whole process) has to sleep—or, in the case of select/poll, the Python process waits simultaneously for "can write" or "data available", writes to the process's stdin only when there is room, and reads its stdout and/or stderr only when data are ready. The proc.communicate() code (actually _communicate where the hairy cases are handled) returns once all stdin data (if any) have been sent and all stdout and/or stderr data have been accumulated.

If you want to read both stdout and stderr on two different pipes (regardless of any stdin redirection), you will need to avoid deadlock too. The deadlock scenario here is different—it occurs when the subprocess writes something long to stderr while you're pulling data from stdout, or vice versa—but it's still there.


The Demo

I promised to demonstrate that, un-redirected, Python subprocesses write to the underlying stdout, not sys.stdout. So, here is some code:

from cStringIO import StringIO
import os
import subprocess
import sys

def show1():
   print 'start show1'
   save = sys.stdout
   sys.stdout = StringIO()
   print 'sys.stdout being buffered'
   proc = subprocess.Popen(['echo', 'hello'])
   proc.wait()
   in_stdout = sys.stdout.getvalue()
   sys.stdout = save
   print 'in buffer:', in_stdout

def show2():
   print 'start show2'
   save = sys.stdout
   sys.stdout = open(os.devnull, 'w')
   print 'after redirect sys.stdout'
   proc = subprocess.Popen(['echo', 'hello'])
   proc.wait()
   sys.stdout = save

show1()
show2()

When run:

$ python out.py
start show1
hello
in buffer: sys.stdout being buffered

start show2
hello

Note that the first routine will fail if you add stdout=sys.stdout, as a StringIO object has no fileno. The second will omit the hello if you add stdout=sys.stdout since sys.stdout has been redirected to os.devnull.

(If you redirect Python's file-descriptor-1, the subprocess will follow that redirection. The open(os.devnull, 'w') call produces a stream whose fileno() is greater than 2.)

10
  • Hmm. Your demo seems to show the opposite of the claim in the end. You're re-directing Python's stdout into the buffer but the subprocess stdout is still going to the console. How is that useful? Am I missing something?
    – Guy Sirton
    Commented Aug 24, 2013 at 21:15
  • @GuySirton: the demo shows that subprocess stdout (when not explicitly directed to sys.stdout) goes to Python's stdout, not the python program's (sys.) stdout. Which I admit is an ... odd distinction. Is there a better way to phrase this?
    – torek
    Commented Aug 24, 2013 at 21:17
  • 2
    +1, good explanation but it lacks the concrete code examples. Here's asyncio-based code that implements the "hard part" (it handles multiple pipes concurrently) in a portable way. You could compare it to the code that uses multiple threads (teed_call()) to do the same.
    – jfs
    Commented Sep 30, 2014 at 17:26
  • 1
    @SamirAguiar: I don't know of any good short summary, but it's pretty simple: at the POSIX OS level, "stdout" is simply "file descriptor #1". When you open a file, you get the next available fd, starting normally from 3 (because 0, 1, and 2 are stdin, stdout, stderr). If you then set up Python's sys.stdout to write to that—e.g., to fd 5 from your most recent open operation—and then fork and exec, the thing you exec is going to write to its fd#1. Unless you make special arrangements, their fd1 is your fd1, which is no longer your sys.stdout.
    – torek
    Commented Apr 14, 2021 at 20:38
  • 1
    @SamirAguiar I think if you read the sys module's documentation it may help clarify it. I think the choice of names is unfortunate (confusing), but, e.g., sys.stdout is pretty much nothing more than "where the output of print will go." So, when you reassign it, you're not actually re-binding file descriptor 1 (which is what the subprocess sees). The sys.__stdout__ is "Python's actual stdout."
    – user98761
    Commented Nov 14, 2022 at 0:33
31

We can also use the default file iterator for reading stdout instead of using iter construct with readline().

import subprocess
import sys

process = subprocess.Popen(
    your_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
for line in process.stdout:
    sys.stdout.write(line)
7
  • 3
    The most elegant answer here!
    – Nir
    Commented Feb 21, 2019 at 14:53
  • 40
    This solution does not display in real time. It waits till the process is done and displays all the output at once. In Viktor Kerkez's solution, if "your_command" displays progressively, the output follows progressively, as long as "your_command" flushes stdout from time to time (because of the pipe).
    – Eric H.
    Commented Apr 9, 2019 at 11:13
  • 4
    @Nir because it's not live.
    – melMass
    Commented Nov 4, 2019 at 17:00
  • 3
    This solution iterates on the default descriptor, so it will only update when a line updates in the output. For a character based update you need to iterate on read() method as shown in Viktor's solution. But that was an overkill for my use case.
    – Jughead
    Commented Nov 13, 2019 at 0:48
  • 1
    Quite real time, no need to wait for the process to exit. Thanks a lot Commented Mar 18, 2021 at 20:12
24

In addition to all these answer, one simple approach could also be as follows:

process = subprocess.Popen(your_command, stdout=subprocess.PIPE)

while process.stdout.readable():
    line = process.stdout.readline()

    if not line:
        break

    print(line.strip())

Loop through the readable stream as long as it's readable and if it gets an empty result, stop.

The key here is that readline() returns a line (with \n at the end) as long as there's an output and empty if it's really at the end.

Hope this helps someone.

1
  • Rather than print(line.strip()), probably better to print(line, end="") (default end="\n") in case the line actually begins or ends with whitespace. And maybe flush=True as well, just in case those lines don't end in "\n" Commented Nov 15, 2023 at 22:29
14

If all you need is that the output will be visible on the console the easiest solution for me was to pass the following arguments to Popen

with Popen(cmd, stdout=sys.stdout, stderr=sys.stderr) as proc:

which will use your python scripts stdio file handles

1
  • Yep, that's what I did -- 'subprocess.run(strCommand.split(), stdout=sys.stdout, stderr=sys.stderr)' I was spawning a long-running process with lots of continuous output, and the output was relayed to the screen in realtime during process execution, not after. So works fine. (Python 3.11.2)
    – maratbn
    Commented Jul 18 at 5:22
12

If you're able to use third-party libraries, You might be able to use something like sarge (disclosure: I'm its maintainer). This library allows non-blocking access to output streams from subprocesses - it's layered over the subprocess module.

2
  • Fine work on sarge, BTW. That does indeed solve the OP's requirement, but might be a bit heavy handed for that use-case. Commented Nov 24, 2017 at 15:16
  • 15
    If you are suggesting a tool at least show an example of usage for this exact case.
    – Serhiy
    Commented Jul 8, 2020 at 13:48
8

Solution 1: Log stdout AND stderr concurrently in realtime

A simple solution which logs both stdout AND stderr concurrently, line-by-line in realtime into a log file.

import subprocess as sp
from concurrent.futures import ThreadPoolExecutor


def log_popen_pipe(p, stdfile):

    with open("mylog.txt", "w") as f:

        while p.poll() is None:
            f.write(stdfile.readline())
            f.flush()

        # Write the rest from the buffer
        f.write(stdfile.read())


with sp.Popen(["ls"], stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    with ThreadPoolExecutor(2) as pool:
        r1 = pool.submit(log_popen_pipe, p, p.stdout)
        r2 = pool.submit(log_popen_pipe, p, p.stderr)
        r1.result()
        r2.result()

Solution 2: A function read_popen_pipes() that allows you to iterate over both pipes (stdout/stderr), concurrently in realtime

import subprocess as sp
from queue import Queue, Empty
from concurrent.futures import ThreadPoolExecutor


def enqueue_output(file, queue):
    for line in iter(file.readline, ''):
        queue.put(line)
    file.close()


def read_popen_pipes(p):

    with ThreadPoolExecutor(2) as pool:
        q_stdout, q_stderr = Queue(), Queue()

        pool.submit(enqueue_output, p.stdout, q_stdout)
        pool.submit(enqueue_output, p.stderr, q_stderr)

        while True:

            if p.poll() is not None and q_stdout.empty() and q_stderr.empty():
                break

            out_line = err_line = ''

            try:
                out_line = q_stdout.get_nowait()
                err_line = q_stderr.get_nowait()
            except Empty:
                pass

            yield (out_line, err_line)

# The function in use:

with sp.Popen(["ls"], stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    for out_line, err_line in read_popen_pipes(p):
        print(out_line, end='')
        print(err_line, end='')

    p.poll()

2
  • Thank you for "read_popen_pipes". It works like a charm and is easy to use even for a Python threading newbie like myself. Note for others: "return p.poll()" assumes the code is being run inside a function. To make it run as a standalone sample, just replace "return p.poll()" with "sys.exit(p.poll())" Also, replace "my_cmd" with ["ls"] or whatever command you want to run.
    – DoomGoober
    Commented Oct 6, 2020 at 1:30
  • 1
    @DoomGoober Thanks for the kind words. I've fixed the code like you suggested. I left sys.exit out to keep the example as simple as possible.
    – Rotareti
    Commented Oct 6, 2020 at 8:13
5

Similar to previous answers but the following solution worked for me on windows using Python3 to provide a common method to print and log in realtime (source)

def print_and_log(command, logFile):
    with open(logFile, 'wb') as f:
        command = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)

        while True:
            output = command.stdout.readline()
            if not output and command.poll() is not None:
                f.close()
                break
            if output:
                f.write(output)
                print(str(output.strip(), 'utf-8'), flush=True)
        return command.poll()
1
  • If I also want to return the stdout in the end what would I modify?
    – Kvothe
    Commented Feb 24, 2021 at 14:58
3

A good but "heavyweight" solution is to use Twisted - see the bottom.

If you're willing to live with only stdout something along those lines should work:

import subprocess
import sys
popenobj = subprocess.Popen(["ls", "-Rl"], stdout=subprocess.PIPE)
while not popenobj.poll():
   stdoutdata = popenobj.stdout.readline()
   if stdoutdata:
      sys.stdout.write(stdoutdata)
   else:
      break
print "Return code", popenobj.returncode

(If you use read() it tries to read the entire "file" which isn't useful, what we really could use here is something that reads all the data that's in the pipe right now)

One might also try to approach this with threading, e.g.:

import subprocess
import sys
import threading

popenobj = subprocess.Popen("ls", stdout=subprocess.PIPE, shell=True)

def stdoutprocess(o):
   while True:
      stdoutdata = o.stdout.readline()
      if stdoutdata:
         sys.stdout.write(stdoutdata)
      else:
         break

t = threading.Thread(target=stdoutprocess, args=(popenobj,))
t.start()
popenobj.wait()
t.join()
print "Return code", popenobj.returncode

Now we could potentially add stderr as well by having two threads.

Note however the subprocess docs discourage using these files directly and recommends to use communicate() (mostly concerned with deadlocks which I think isn't an issue above) and the solutions are a little klunky so it really seems like the subprocess module isn't quite up to the job (also see: http://www.python.org/dev/peps/pep-3145/ ) and we need to look at something else.

A more involved solution is to use Twisted as shown here: https://twistedmatrix.com/documents/11.1.0/core/howto/process.html

The way you do this with Twisted is to create your process using reactor.spawnprocess() and providing a ProcessProtocol that then processes output asynchronously. The Twisted sample Python code is here: https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.py

6
  • Thanks! I just tried something like this (based on @PauloAlmeida 's comment, but my call to subprocess.Popen is blocking -- i.e. it only comes to the while loop once it returns... Commented Aug 24, 2013 at 18:59
  • 1
    That's not what's going on. It's entering the while loop right away then blocking on the read() call until the subprocess exits and the parent process receives EOF on the pipe.
    – Alp
    Commented Aug 24, 2013 at 19:00
  • @Alp interesting! so it is. Commented Aug 24, 2013 at 19:02
  • Yeah, I was too quick to post this. It actually doesn't work properly and can't be easily fixed. back to the drawing table.
    – Guy Sirton
    Commented Aug 24, 2013 at 19:02
  • 1
    @zhermes: So the problem with read() is that it will try to read the entire output till EOF which isn't useful. readline() helps and may be all you need (really long lines can also be a problem though). You also need to watch out for buffering in the process you're launching...
    – Guy Sirton
    Commented Aug 24, 2013 at 21:01
3

Why not set stdout directly to sys.stdout? And if you need to output to a log as well, then you can simply override the write method of f.

import sys
import subprocess

class SuperFile(open.__class__):

    def write(self, data):
        sys.stdout.write(data)
        super(SuperFile, self).write(data)

f = SuperFile("log.txt","w+")       
process = subprocess.Popen(command, stdout=f, stderr=f)
1
  • 1
    That wouldn't work: the subprocess module forks and sets the stdout file descriptor to the file descriptor of the passed file object. The write-method would never be called (at least that's what subprocess does for stderr, I gues it's the same for stdout).
    – t.animal
    Commented May 9, 2017 at 14:13
3

Based on all the above I suggest a slightly modified version (python3):

  • while loop calling readline (The iter solution suggested seemed to block forever for me - Python 3, Windows 7)
  • structered so handling of read data does not need to be duplicated after poll returns not-None
  • stderr piped into stdout so both output outputs are read
  • Added code to get exit value of cmd.

Code:

import subprocess
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
                        stderr=subprocess.STDOUT, universal_newlines=True)
while True:
    rd = proc.stdout.readline()
    print(rd, end='')  # and whatever you want to do...
    if not rd:  # EOF
        returncode = proc.poll()
        if returncode is not None:
            break
        time.sleep(0.1)  # cmd closed stdout, but not exited yet

# You may want to check on ReturnCode here
0
3

None of the Pythonic solutions worked for me. It turned out that proc.stdout.read() or similar may block forever.

Therefore, I use tee like this:

subprocess.run('./my_long_running_binary 2>&1 | tee -a my_log_file.txt && exit ${PIPESTATUS}', shell=True, check=True, executable='/bin/bash')

This solution is convenient if you are already using shell=True.

${PIPESTATUS} captures the success status of the entire command chain (only available in Bash). If I omitted the && exit ${PIPESTATUS}, then this would always return zero since tee never fails.

unbuffer might be necessary for printing each line immediately into the terminal, instead of waiting way too long until the "pipe buffer" gets filled. However, unbuffer swallows the exit status of assert (SIG Abort)...

2>&1 also logs stderror to the file.

3

I found a simple solution to a much complicated problem.

  1. Both stdout and stderr need to be streamed.
  2. Both of them need to be non-blocking: when there is no output and when there are too much output.
  3. Do not want to use Threading or multiprocessing, also not willing to use pexpect.

This solution uses a gist I found here

import subprocess as sbp
import fcntl
import os

def non_block_read(output):
    fd = output.fileno()
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
    try:
        return output.readline()
    except:
        return ""

with sbp.Popen('find / -name fdsfjdlsjf',
                shell=True,
                universal_newlines=True,
                encoding='utf-8',
                bufsize=1,
                stdout=sbp.PIPE,
                stderr=sbp.PIPE) as p:
    while True:
        out = non_block_read(p.stdout)
        err = non_block_read(p.stderr)
        if out:
            print(out, end='')
        if err:
            print('E: ' + err, end='')
        if p.poll() is not None:
            break
0
2

It looks like line-buffered output will work for you, in which case something like the following might suit. (Caveat: it's untested.) This will only give the subprocess's stdout in real time. If you want to have both stderr and stdout in real time, you'll have to do something more complex with select.

proc = subprocess.Popen(run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
while proc.poll() is None:
    line = proc.stdout.readline()
    print line
    log_file.write(line + '\n')
# Might still be data on stdout at this point.  Grab any
# remainder.
for line in proc.stdout.read().split('\n'):
    print line
    log_file.write(line + '\n')
# Do whatever you want with proc.stderr here...
1
  • Does not show live output. I get output first, if process is finished or terminated.
    – rundekugel
    Commented Mar 7 at 20:51
2

All of the above solutions I tried failed either to separate stderr and stdout output, (multiple pipes) or blocked forever when the OS pipe buffer was full which happens when the command you are running outputs too fast (there is a warning for this on python poll() manual of subprocess). The only reliable way I found was through select, but this is a posix-only solution:

import subprocess
import sys
import os
import select
# returns command exit status, stdout text, stderr text
# rtoutput: show realtime output while running
def run_script(cmd,rtoutput=0):
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    poller = select.poll()
    poller.register(p.stdout, select.POLLIN)
    poller.register(p.stderr, select.POLLIN)

    coutput=''
    cerror=''
    fdhup={}
    fdhup[p.stdout.fileno()]=0
    fdhup[p.stderr.fileno()]=0
    while sum(fdhup.values()) < len(fdhup):
        try:
            r = poller.poll(1)
        except select.error, err:
            if err.args[0] != EINTR:
                raise
            r=[]
        for fd, flags in r:
            if flags & (select.POLLIN | select.POLLPRI):
                c = os.read(fd, 1024)
                if rtoutput:
                    sys.stdout.write(c)
                    sys.stdout.flush()
                if fd == p.stderr.fileno():
                    cerror+=c
                else:
                    coutput+=c
            else:
                fdhup[fd]=1
    return p.poll(), coutput.strip(), cerror.strip()
2
  • Another alternative is to spin off one thread per pipe. Each thread can do blocking I/O on the pipe, without blocking the other thread(s). But this introduces its own set of issues. All methods have annoyances, you just pick which one(s) you find least annoying. :-)
    – torek
    Commented May 25, 2017 at 15:21
  • Does not work for me TypeError: can only concatenate str (not "bytes") to str -Python 3.8.5
    – Kvothe
    Commented Feb 24, 2021 at 15:00
2

I think that the subprocess.communicate method is a bit misleading: it actually fills the stdout and stderr that you specify in the subprocess.Popen.

Yet, reading from the subprocess.PIPE that you can provide to the subprocess.Popen's stdout and stderr parameters will eventually fill up OS pipe buffers and deadlock your app (especially if you've multiple processes/threads that must use subprocess).

My proposed solution is to provide the stdout and stderr with files - and read the files' content instead of reading from the deadlocking PIPE. These files can be tempfile.NamedTemporaryFile() - which can also be accessed for reading while they're being written into by subprocess.communicate.

Below is a sample usage:

try:
    with ProcessRunner(
        ("python", "task.py"), env=os.environ.copy(), seconds_to_wait=0.01
    ) as process_runner:
        for out in process_runner:
            print(out)
except ProcessError as e:
    print(e.error_message)
    raise

And this is the source code which is ready to be used with as many comments as I could provide to explain what it does:

If you're using python 2, please make sure to first install the latest version of the subprocess32 package from pypi.

import os
import sys
import threading
import time
import tempfile
import logging

if os.name == 'posix' and sys.version_info[0] < 3:
    # Support python 2
    import subprocess32 as subprocess
else:
    # Get latest and greatest from python 3
    import subprocess

logger = logging.getLogger(__name__)


class ProcessError(Exception):
    """Base exception for errors related to running the process"""


class ProcessTimeout(ProcessError):
    """Error that will be raised when the process execution will exceed a timeout"""


class ProcessRunner(object):
    def __init__(self, args, env=None, timeout=None, bufsize=-1, seconds_to_wait=0.25, **kwargs):
        """
        Constructor facade to subprocess.Popen that receives parameters which are more specifically required for the
        Process Runner. This is a class that should be used as a context manager - and that provides an iterator
        for reading captured output from subprocess.communicate in near realtime.

        Example usage:


        try:
            with ProcessRunner(('python', task_file_path), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner:
                for out in process_runner:
                    print(out)
        except ProcessError as e:
            print(e.error_message)
            raise

        :param args: same as subprocess.Popen
        :param env: same as subprocess.Popen
        :param timeout: same as subprocess.communicate
        :param bufsize: same as subprocess.Popen
        :param seconds_to_wait: time to wait between each readline from the temporary file
        :param kwargs: same as subprocess.Popen
        """
        self._seconds_to_wait = seconds_to_wait
        self._process_has_timed_out = False
        self._timeout = timeout
        self._process_done = False
        self._std_file_handle = tempfile.NamedTemporaryFile()
        self._process = subprocess.Popen(args, env=env, bufsize=bufsize,
                                         stdout=self._std_file_handle, stderr=self._std_file_handle, **kwargs)
        self._thread = threading.Thread(target=self._run_process)
        self._thread.daemon = True

    def __enter__(self):
        self._thread.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self._thread.join()
        self._std_file_handle.close()

    def __iter__(self):
        # read all output from stdout file that subprocess.communicate fills
        with open(self._std_file_handle.name, 'r') as stdout:
            # while process is alive, keep reading data
            while not self._process_done:
                out = stdout.readline()
                out_without_trailing_whitespaces = out.rstrip()
                if out_without_trailing_whitespaces:
                    # yield stdout data without trailing \n
                    yield out_without_trailing_whitespaces
                else:
                    # if there is nothing to read, then please wait a tiny little bit
                    time.sleep(self._seconds_to_wait)

            # this is a hack: terraform seems to write to buffer after process has finished
            out = stdout.read()
            if out:
                yield out

        if self._process_has_timed_out:
            raise ProcessTimeout('Process has timed out')

        if self._process.returncode != 0:
            raise ProcessError('Process has failed')

    def _run_process(self):
        try:
            # Start gathering information (stdout and stderr) from the opened process
            self._process.communicate(timeout=self._timeout)
            # Graceful termination of the opened process
            self._process.terminate()
        except subprocess.TimeoutExpired:
            self._process_has_timed_out = True
            # Force termination of the opened process
            self._process.kill()

        self._process_done = True

    @property
    def return_code(self):
        return self._process.returncode



1

Here is a class which I'm using in one of my projects. It redirects output of a subprocess to the log. At first I tried simply overwriting the write-method but that doesn't work as the subprocess will never call it (redirection happens on filedescriptor level). So I'm using my own pipe, similar to how it's done in the subprocess-module. This has the advantage of encapsulating all logging/printing logic in the adapter and you can simply pass instances of the logger to Popen: subprocess.Popen("/path/to/binary", stderr = LogAdapter("foo"))

class LogAdapter(threading.Thread):

    def __init__(self, logname, level = logging.INFO):
        super().__init__()
        self.log = logging.getLogger(logname)
        self.readpipe, self.writepipe = os.pipe()

        logFunctions = {
            logging.DEBUG: self.log.debug,
            logging.INFO: self.log.info,
            logging.WARN: self.log.warn,
            logging.ERROR: self.log.warn,
        }

        try:
            self.logFunction = logFunctions[level]
        except KeyError:
            self.logFunction = self.log.info

    def fileno(self):
        #when fileno is called this indicates the subprocess is about to fork => start thread
        self.start()
        return self.writepipe

    def finished(self):
       """If the write-filedescriptor is not closed this thread will
       prevent the whole program from exiting. You can use this method
       to clean up after the subprocess has terminated."""
       os.close(self.writepipe)

    def run(self):
        inputFile = os.fdopen(self.readpipe)

        while True:
            line = inputFile.readline()

            if len(line) == 0:
                #no new data was added
                break

            self.logFunction(line.strip())

If you don't need logging but simply want to use print() you can obviously remove large portions of the code and keep the class shorter. You could also expand it by an __enter__ and __exit__ method and call finished in __exit__ so that you could easily use it as context.

1
import os

def execute(cmd, callback):
    for line in iter(os.popen(cmd).readline, ''): 
            callback(line[:-1])

execute('ls -a', print)
1

Had the same problem and worked out a simple and clean solution using process.sdtout.read1() which works perfectly for my needs in python3.

Here is a demo using the ping command (requires internet connection):

from subprocess import Popen, PIPE

cmd = "ping 8.8.8.8"
proc = Popen([cmd], shell=True, stdout=PIPE)
while True:
    print(proc.stdout.read1())

Every second or so a new line is printed in the python console as the ping command reports its data in real time.

1

I made a small correction towards this answer by @Rotareti, and it works fine for process generating only stderr. The original answer used one try-catch block which blocked obtaining stderr outputs when stdout is empty. I also add support for timeout here.

# https://stackoverflow.com/a/57084403
from typing import List, Tuple
import sys, io, queue, psutil
import subprocess
from concurrent.futures import ThreadPoolExecutor

    
class Shell:
    def __init__(
        self,
        shell_exec: bool = True,
        print_out: bool = True,
        print_cmd: bool = True,
        print_file: io.TextIOWrapper | None = None,
        return_list: bool = False,
    ) -> None:
        self.shell_exec = shell_exec
        self.print_out = print_out
        self.print_cmd = print_cmd
        self.print_file = print_file
        self.return_list = return_list


    def _read_popen_pipes(self, p: subprocess.Popen, timeout_sec: float|None = None):

        def _enqueue_output(file: io.TextIOWrapper, q: queue.Queue):
            for line in iter(file.readline, ''):
                q.put(line)
            file.close()

        def _timeout():
            try:
                p.wait(timeout=timeout_sec)
            except subprocess.TimeoutExpired:
                parent = psutil.Process(p.pid)
                for child in parent.children(recursive=True):
                    child.terminate()
                parent.terminate()

        with ThreadPoolExecutor(3) as pool:
            q_stdout, q_stderr = queue.Queue(), queue.Queue()

            if timeout_sec is not None:
                pool.submit(_timeout)
            pool.submit(_enqueue_output, p.stdout, q_stdout)
            pool.submit(_enqueue_output, p.stderr, q_stderr)

            while p.poll() is None or not q_stdout.empty() or not q_stderr.empty():
                out_line = err_line = ''

                try:
                    out_line = q_stdout.get_nowait()
                except queue.Empty:
                    pass

                try:
                    err_line = q_stderr.get_nowait()
                except queue.Empty:
                    pass

                yield (out_line, err_line)
    

    def run(self, cmd: str | List[str], timeout: float|None = None) -> Tuple[str|List[str], str|List[str], int]:
        with subprocess.Popen(
            cmd, shell=self.shell_exec, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,
        ) as p:
            if self.print_cmd:
                if self.print_out:
                    print(f'+ {cmd}', file=sys.stderr, flush=True)
                if self.print_file:
                    print(f'+ {cmd}', file=self.print_file, flush=True)
            out: List[str] = []
            err: List[str] = []
            for out_line, err_line in self._read_popen_pipes(p, timeout):
                out.append(out_line)
                err.append(err_line)
                if self.print_out:
                    print(out_line, end='', flush=True)
                    print(err_line, end='', file=sys.stderr, flush=True)
                if self.print_file:
                    print(out_line, end='', flush=True, file=self.print_file)
                    print(err_line, end='', flush=True, file=self.print_file)
            # end for
            if self.return_list:
                return out, err, p.returncode
            else:
                return ''.join(out), ''.join(err), p.returncode


if __name__ == '__main__':
    Shell().run('''echo '#!/bin/bash

for i in {1..10}
do
    echo "Sleep $i to stdout" >> /dev/stdout
    echo "Sleep $i to stderr" >> /dev/stderr
    sleep 1
done
' > sleep.sh && chmod +x sleep.sh''')

    out, err, code = Shell().run('./sleep.sh', timeout=2)
    print(f'{out = }')
    print(f'{err = }')
    print(f'{code = }')

    Shell().run('rm sleep.sh')
1

Handling the live output stream of a command can be achieved by iterating over stdout as the subprocess.Popen runs.

This implementation:

  • uses a with-statement such that standard file descriptors are closed, and the process is waited for
  • propagates keyword arguments to the subprocess constructor
  • defaults to text=True to automatically decode bytestrings into strings
  • raises a CalledProcessError upon failure if check=True like subprocess.run does
  • returns a CompletedProcess upon success like subprocess.run does
  • uses two threads to handle stdout and stderr concurrently (for a version redirecting stdout to stderr without threads see my simplified answer)
import logging
from collections import deque
from concurrent.futures import ThreadPoolExecutor
from functools import partial
from subprocess import PIPE, CalledProcessError, CompletedProcess, Popen


def stream_command(
    args,
    *,
    stdout_handler=logging.info,
    stderr_handler=logging.error,
    check=True,
    text=True,
    stdout=PIPE,
    stderr=PIPE,
    **kwargs,
):
    """Mimic subprocess.run, while processing the command output in real time."""
    with (
        Popen(args, text=text, stdout=stdout, stderr=stderr, **kwargs) as process,
        ThreadPoolExecutor(2) as pool,  # two threads to handle the (live) streams separately
    ):
        exhaust = partial(deque, maxlen=0)  # collections recipe: exhaust an iterable at C-speed
        exhaust_async = partial(pool.submit, exhaust)  # exhaust non-blocking in a background thread
        exhaust_async(stdout_handler(line[:-1]) for line in process.stdout)
        exhaust_async(stderr_handler(line[:-1]) for line in process.stderr)
    retcode = process.poll()  # block until both iterables are exhausted (process finished)
    if check and retcode:
        raise CalledProcessError(retcode, process.args)
    return CompletedProcess(process.args, retcode)

Logging to a file then becomes as simple as setting up logging:

logging.basicConfig(
    level=logging.INFO,
    filename="./capture.log",
    filemode="w",
    encoding="utf-8",
)
logging.info("test from python")
stream_command(["echo", "test from subprocess"])

With the resulting file:

$ cat ./capture.log
INFO:root:test from python
INFO:root:test from subprocess

The behaviour can be tweaked to preference (print instead of logging.info, or both, etc.):

stream_command(["echo", "test"])
# INFO:root:test
stream_command("cat ./nonexist", shell=True, check=False)
# ERROR:root:cat: ./nonexist: No such file or directory
stream_command(["echo", "test"], stdout_handler=print)
# test
stdout_lines = []
def handler(line):
    print(line)
    logging.info(line)
    stdout_lines.append(line)
stream_command(["echo", "test"], stdout_handler=handler)
# test
# INFO:root:test
print(stdout_lines)
# ['test']
0

In my view "live output from subprocess command" means that both stdout and stderr should be live. And stdin should also be delivered to the subprocess.

The fragment below produces live output on stdout and stderr and also captures them as bytes in outcome.{stdout,stderr}.

The trick involves proper use of select and poll.

Works well for me on Python 3.9.


        if self.log == 1:
            print(f"** cmnd= {fullCmndStr}")

        self.outcome.stdcmnd = fullCmndStr
        try:
            process = subprocess.Popen(
                fullCmndStr,
                shell=True,
                encoding='utf8',
                executable="/bin/bash",
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
            )
        except OSError:
            self.outcome.error = OSError
        else:
            process.stdin.write(stdin)
            process.stdin.close() # type: ignore

        stdoutStrFile = io.StringIO("")
        stderrStrFile = io.StringIO("")

        pollStdout = select.poll()
        pollStderr = select.poll()

        pollStdout.register(process.stdout, select.POLLIN)
        pollStderr.register(process.stderr, select.POLLIN)

        stdoutEOF = False
        stderrEOF = False

        while True:
            stdoutActivity = pollStdout.poll(0)
            if stdoutActivity:
                c= process.stdout.read(1)
                if c:
                    stdoutStrFile.write(c)
                    if self.log == 1:
                        sys.stdout.write(c)
                else:
                   stdoutEOF = True

            stderrActivity = pollStderr.poll(0)
            if stderrActivity:
                c= process.stderr.read(1)
                if c:
                    stderrStrFile.write(c)
                    if self.log == 1:
                        sys.stderr.write(c)
                else:
                   stderrEOF = True
            if stdoutEOF and stderrEOF:
                break

        if self.log == 1:
            print(f"** cmnd={fullCmndStr}")

        process.wait() # type: ignore

        self.outcome.stdout = stdoutStrFile.getvalue()
        self.outcome.stderr = stderrStrFile.getvalue()
        self.outcome.error = process.returncode # type: ignore
0

The only way i've found how to read a subprocess' output in a streaming fashion (while also capturing it in a variable) in Python (for multiple output streams, i.e. both stdout and stderr) is by passing the subprocess a named temporary file to write to and then opening the same temporary file in a separate reading handle.

Note: this is for Python 3

    stdout_write = tempfile.NamedTemporaryFile()
    stdout_read = io.open(stdout_write.name, "r")
    stderr_write = tempfile.NamedTemporaryFile()
    stderr_read = io.open(stderr_write.name, "r")

    stdout_captured = ""
    stderr_captured = ""

    proc = subprocess.Popen(["command"], stdout=stdout_write, stderr=stderr_write)
    while True:
        proc_done: bool = cli_process.poll() is not None

        while True:
            content = stdout_read.read(1024)
            sys.stdout.write(content)
            stdout_captured += content
            if len(content) < 1024:
                break

        while True:
            content = stderr_read.read(1024)
            sys.stderr.write(content)
            stdout_captured += content
            if len(content) < 1024:
                break

        if proc_done:
            break

        time.sleep(0.1)

    stdout_write.close()
    stdout_read.close()
    stderr_write.close()
    stderr_read.close()

However, if you don't need to capture the output, then you can simply pass sys.stdout and sys.stderr streams from your Python script to the called subprocess, as xaav suggested in his answer :

subprocess.Popen(["command"], stdout=sys.stdout, stderr=sys.stderr)
0

This is an old post, but in Python 3 -- tested in Python 3.11 -- the following code worked for me, to stream live or "real time" output using the subprocess module:

import sys
from os import fdopen
from subprocess import Popen, PIPE, STDOUT


with Popen(command,
           shell=True,
           stdout=PIPE,
           stderr=STDOUT) as sp:

    with fdopen(sys.stdout.fileno(), 'wb', closefd=False) as stdout:

        for line in sp.stdout:
            stdout.write(line)
            stdout.flush()

Convenience Function

As it's idiomatic, I usually create a convenience function run to chain a list of commands in the terminal and stream the output in real time.

Note that I use && as the separator here, but you could easily use another one, such as ; if you don't want to fail early on error, or even & instead.

import sys
from os import fdopen
from subprocess import Popen, PIPE, STDOUT

def run(cmds, join='&&'):
    with Popen(join.join(cmds),
               shell=True,
               stdout=PIPE,
               stderr=STDOUT) as sp:
        with fdopen(sys.stdout.fileno(), 'wb', closefd=False) as stdout:
            for line in sp.stdout:
                stdout.write(line)
                stdout.flush()

Usage is like so:

commands = [
    'echo hello',
    'sleep 3',
    'echo world',
    'sleep 2',
    'echo !',
]
run(commands)
2
  • looks like it would work but doesn't seem to do it for me.
    – thang
    Commented Jul 30 at 1:17
  • weird, it works on a mac for me. any logs or output on your end? Commented Aug 20 at 16:00
0

None of them worked for me. Python3.11 / Python3.12 Linux/Windows11

I get the stdout first, if the command is finished. For Linux I found:

p=pexpect.spawn(cmd) / p.read_nonblocking()

But unfortunately, this doesn't work for Windows. I'm still searching for a solution for Windows.

0

simply don't use any pipes:

ret_val = subprocess.Popen(run_command, stderr=subprocess.PIPE, bufsize = 1, universal_newlines = True, shell=True)

see the docs:

stdin, stdout and stderr specify the executed program’s standard input, standard output and standard error file handles, respectively. Valid values are None, PIPE, DEVNULL, an existing file descriptor (a positive integer), and an existing file object with a valid file descriptor. With the default settings of None, no redirection will occur.

https://docs.python.org/3/library/subprocess.html#subprocess.PIPE

0

python 3:

def init_realtime_console():
    try:
        command = f'cmd /c "<YOUR_COMMAND>"'

        # Run in the current working directory
        process = subprocess.Popen(
            f'{command}',
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            encoding="utf-8",
            cwd=os.getcwd()
        )

        # Reading output in real-time
        try:
            while True:
                stdout_line = process.stdout.readline()

                # Print lines if available
                if stdout_line:
                    print(stdout_line, end='')

                # Break when the process ends and buffers are empty
                if not stdout_line and process.poll() is not None:
                    break
        except KeyboardInterrupt:
            process.terminate()
            raise

        # Wait for the process to finish
        process.stdout.close()
        process.wait()

        # Check if the command was successful
        if process.returncode == 0:
            print("Successfully!")
        else:
            print("Failed")
            
    except Exception as e:
        print(f"An unexpected error occurred: {e}")
-1

Works for me (Python 3.12):

import subprocess
from time import sleep

def run_shell(command):

    def printShellOut(process):
        out = process.stdout.readline()
        if out:
            print(out.decode(), flush = True, end='')
            return True
    
    def printShellErr(process):
        err = process.stderr.readline()
        if err:
            print('Error: ' + err.decode(), flush = True, end='')
            return True
    
    def printShell(process):
        while printShellOut(process):
            pass
        
        while printShellErr(process):
            pass
            
    
    process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    while process.poll() is None:
        printShell(process)
        sleep(0.5)
    
    printShell(process)
4
  • 2
    This question is over a decade old, and it has several answers. It's okay to answer old questions, but it's very hard to tell how this answer is different from the others that already exist. What did you do differently? Are you sure that this answer is different from all other answers? How is this better than them? Why should we use this approach instead of the others?
    – Chris
    Commented Nov 25, 2023 at 14:19
  • @Chris Try them out! I've tried a few .... they are enterly not working, end up in an infinite loop, cause massive processor-load, store to a file and don't give a live-output in the console .... and don't include errors. If there was one working I wouldn't had to search for hours to understand the subprocess-class and for sure wouldn't have posted my solution
    – koallalays
    Commented Dec 5, 2023 at 17:08
  • 2
    You should include that in your answer. Otherwise this is just another code dump that nobody will look look at.
    – Chris
    Commented Dec 5, 2023 at 17:18
  • Does not show live output. I get output first, if process is finished or terminated.
    – rundekugel
    Commented Mar 7 at 20:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.