20

I am trying to grab stdout from a subprocess.Popen call and although I am achieving this easily by doing:

cmd = subprocess.Popen('ls -l', shell=True, stdout=PIPE)
for line in cmd.stdout.readlines():
    print line

I would like to grab stdout in "real time". With the above method, PIPE is waiting to grab all the stdout and then it returns.

So for logging purposes, this doesn't meet my requirements (e.g. "see" what is going on while it happens).

Is there a way to get line by line, stdout while is running? Or is this a limitation of subprocess(having to wait until the PIPE closes).

EDIT If I switch readlines() for readline() I only get the last line of the stdout (not ideal):

In [75]: cmd = Popen('ls -l', shell=True, stdout=PIPE)
In [76]: for i in cmd.stdout.readline(): print i
....: 
t
o
t
a
l

1
0
4
martineau
  • 99,260
  • 22
  • 139
  • 249
alfredodeza
  • 4,690
  • 4
  • 27
  • 42
  • Duplicate: http://stackoverflow.com/questions/1822237/writing-the-command-window-to-the-windows-while-running-an-exe-in-python –  Feb 22 '10 at 11:15
  • related: [Python: read streaming input from subprocess.communicate()](http://stackoverflow.com/q/2715847/4279) – jfs Nov 07 '13 at 15:12
  • With `readline()`, you get the first line, not the last line. You have to repeatedly call `readline()` to get every line. – Albert Dec 28 '14 at 22:34
  • related: [Python subprocess readlines() hangs](http://stackoverflow.com/q/12419198/4279) – jfs Sep 12 '15 at 15:06

8 Answers8

21

Your interpreter is buffering. Add a call to sys.stdout.flush() after your print statement.

Jeff
  • 242
  • 1
  • 2
  • 1
    AMAZING! thank you! this was IT!, I had already given up... this worked perfectly. THANKS AGAIN! woo hoo! – alfredodeza Feb 28 '10 at 00:52
  • 1
    @alfredodeza: it can't possibly work `.readlines()` won't return until *all output* is read (until EOF happens) and therefore adding `sys.stdout.flush()` won't change anything. It contradicts your requirement: *"Is there a way to get line by line, stdout while [the child process] is running?"* – jfs Sep 12 '15 at 15:05
14

Actually, the real solution is to directly redirect the stdout of the subprocess to the stdout of your process.

Indeed, with your solution, you can only print stdout, and not stderr, for instance, at the same time.

import sys
from subprocess import Popen
Popen("./slow_cmd_output.sh", stdout=sys.stdout, stderr=sys.stderr).communicate()

The communicate() is so to make the call blocking until the end of the subprocess, else it would directly go to the next line and your program might terminate before the subprocess (although the redirection to your stdout will still work, even after your python script has closed, I tested it).

That way, for instance, you are redirecting both stdout and stderr, and in absolute real time.

For instance, in my case I tested with this script slow_cmd_output.sh:

#!/bin/bash

for i in 1 2 3 4 5 6; do sleep 5 && echo "${i}th output" && echo "err output num ${i}" >&2; done
Undo
  • 1,025
  • 1
  • 10
  • 14
  • 2
    Note: I know the thread is old, but I came across this thread and the answer did not satisfy me. Having found the answer, I thought I might as well post it :) – Undo Jun 09 '14 at 19:31
  • Are there any pitfalls for this? @Undo – Moshe Apr 24 '18 at 08:49
11

To get output "in real time", subprocess is unsuitable because it can't defeat the other process's buffering strategies. That's the reason I always recommend, whenever such "real time" output grabbing is desired (quite a frequent question on stack overflow!), to use instead pexpect (everywhere but Windows -- on Windows, wexpect).

Alex Martelli
  • 762,786
  • 156
  • 1,160
  • 1,345
  • Expect's line buffering is a topic (recently) dear to my heart; would you mind having a look at my latest question? – Tobu Jan 18 '10 at 02:09
  • @Tobu, sure, had a look and answered (recommending pexpect again). – Alex Martelli Jan 18 '10 at 02:18
  • Also [`winpexpect` module](https://bitbucket.org/geertj/winpexpect/overview) could be used on Windows. – jfs Apr 18 '13 at 19:56
  • you could also [provide a pseudo-tty even while using `subprocess` module](http://stackoverflow.com/a/12471855/4279) – jfs Sep 12 '15 at 15:07
3

As this is a question I searched for an answer to for days, I wanted to leave this here for those who follow. While it is true that subprocess cannot combat the other process's buffering strategy, in the case where you are calling another Python script with subprocess.Popen, you CAN tell it to start an unbuffered python.

command = ["python", "-u", "python_file.py"]
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

for line in iter(p.stdout.readline, ''):
    line = line.replace('\r', '').replace('\n', '')
    print line
    sys.stdout.flush()

I have also seen cases where the popen arguments bufsize=1 and universal_newlines=True have helped with exposing the hidden stdout.

ochawkeye
  • 151
  • 9
  • 1
    you could use `print line,` (note: comma) to avoid stripping newlines (no need `replace('\n','')` or `rstrip(b'\r\n')`). – jfs Sep 24 '15 at 22:50
3

Drop the readlines() which is coalescing the output. Also you'll need to enforce line buffering since most commands will interally buffer output to a pipe. For details see: http://www.pixelbeat.org/programming/stdio_buffering/

pixelbeat
  • 27,785
  • 9
  • 47
  • 57
  • I went through the link but I am not clear how to enforce buffering in Python, would you be able to clarify? – alfredodeza Jan 18 '10 at 11:31
  • You enforce buffering on the command. tail -f line buffers by default. For grep, sed etc. you'll need to pass appropriate options to them. Note also the new stdbuf command which can apply line buffering to any command that uses stdio – pixelbeat Jan 18 '10 at 11:48
1
cmd = subprocess.Popen(["ls", "-l"], stdout=subprocess.PIPE)
for line in cmd.stdout:
    print line.rstrip("\n")
  • 2
    Roger, this is still waiting for the process to end. The way I can confirm this is happening, is by running a longer process like a system update and passing the information to the log. All lines in a 10 second running process are printed/written to the log at the same time – alfredodeza Jan 18 '10 at 11:24
  • You're running into a buffer size problem; take my above code and change the command to `["find", "/"]` and you will see output before that process ends. –  Jan 18 '10 at 16:50
  • I `print(line.rstrip("\n"))` and got `TypeError: a bytes-like object is required, not 'str'` in Python3, do you have some ideas? – ah bon Mar 28 '19 at 03:33
0

The call to readlines is waiting for the process to exit. Replace this with a loop around cmd.stdout.readline() (note singular) and all should be well.

Jakob Borg
  • 21,033
  • 6
  • 45
  • 47
0

As stated already the issue is in the stdio library's buffering of printf like statements when no terminal is attached to the process. There is a way around this on the Windows platform anyway. There may be a similar solution on other platforms as well.

On Windows you can force create a new console at process creation. The good thing is this can remain hidden so you never see it (this is done by shell=True inside the subprocess module).

cmd = subprocess.Popen('ls -l', shell=True, stdout=PIPE, creationflags=_winapi.CREATE_NEW_CONSOLE, bufsize=1, universal_newlines=True)
for line in cmd.stdout.readlines():
    print line

or

A slightly more complete solution is that you explicitly set the STARTUPINFO params which prevents launching a new and unnecessary cmd.exe shell process which shell=True did above.

class PopenBackground(subprocess.Popen):
    def __init__(self, *args, **kwargs):

        si = kwargs.get('startupinfo', subprocess.STARTUPINFO())
        si.dwFlags |= _winapi.STARTF_USESHOWWINDOW
        si.wShowWindow = _winapi.SW_HIDE

        kwargs['startupinfo'] = si
        kwargs['creationflags'] = kwargs.get('creationflags', 0) | _winapi.CREATE_NEW_CONSOLE
        kwargs['bufsize'] = 1
        kwargs['universal_newlines'] = True

        super(PopenBackground, self).__init__(*args, **kwargs)

process = PopenBackground(['ls', '-l'], stdout=subprocess.PIPE)
    for line in cmd.stdout.readlines():
        print line
Chris
  • 9
  • 1
  • child's buffering strategy does not matter as long as you use `.readlines()` that does not return until EOF. Use `for line in iter(cmd.stdout.readline, b''):` instead. [Read my answers to the questions I've linked above](http://stackoverflow.com/questions/2082850/real-time-subprocess-popen-via-stdout-and-pipe/32538935#comment29502226_2082850) – jfs Sep 12 '15 at 21:18