237

To launch programs from my Python-scripts, I'm using the following method:

def execute(command):
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    output = process.communicate()[0]
    exitCode = process.returncode

    if (exitCode == 0):
        return output
    else:
        raise ProcessException(command, exitCode, output)

So when i launch a process like Process.execute("mvn clean install"), my program waits until the process is finished, and only then i get the complete output of my program. This is annoying if i'm running a process that takes a while to finish.

Can I let my program write the process output line by line, by polling the process output before it finishes in a loop or something?

I found this article which might be related.

Neuron
  • 3,776
  • 3
  • 24
  • 44
Wolkenarchitekt
  • 17,351
  • 28
  • 102
  • 166

13 Answers13

307

You can use iter to process lines as soon as the command outputs them: lines = iter(fd.readline, ""). Here's a full example showing a typical use case (thanks to @jfs for helping out):

from __future__ import print_function # Only Python 2.x
import subprocess

def execute(cmd):
    popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True)
    for stdout_line in iter(popen.stdout.readline, ""):
        yield stdout_line 
    popen.stdout.close()
    return_code = popen.wait()
    if return_code:
        raise subprocess.CalledProcessError(return_code, cmd)

# Example
for path in execute(["locate", "a"]):
    print(path, end="")
tokland
  • 60,415
  • 12
  • 129
  • 162
  • 30
    I've tried this code (with a program that takes significant time to run) and can confirm it outputs lines as they're received, rather than waiting for execution to complete. This is the superior answer imo. – Andrew Martin May 13 '14 at 18:08
  • 17
    Note: In Python 3, you could use `for line in popen.stdout: print(line.decode(), end='')`. To support both Python 2 and 3, use bytes literal: `b''` otherwise `lines_iterator` never ends on Python 3. – jfs Feb 03 '15 at 17:05
  • 5
    The problem with this approach is that if the process pauses for a bit without writing anything to stdout there is no more input to read. You will need a loop to check whether or not the process has finished. I tried this using subprocess32 on python 2.7 – Har Dec 26 '15 at 19:05
  • 1
    @Har: wrong. The loop ends (on EOF) when the subprocess is dead. No need to check whether the process is alive or not. – jfs Mar 08 '16 at 18:18
  • Do not use `PIPE` unless you read from the pipe *while the process is running* otherwise the child process may hang (your edit that introduced `stderr=PIPE` is wrong). To read more than one pipe, you need more complex code (threads, async.IO). – jfs May 01 '16 at 11:54
  • 7
    it should work. To polish it, you could add `bufsize=1` (it may improve performance on Python 2), close the `popen.stdout` pipe explicitly (without waiting for the garbage collection to take care of it), and raise `subprocess.CalledProcessError` (like `check_call()`, `check_output()` do). The `print` statement is different on Python 2 and 3: you could use the softspace hack `print line,` (note: comma) to avoid doubling all newlines like your code does and passing `universal_newlines=True` on Python 3, to get text instead of bytes—[related answer](http://stackoverflow.com/a/17698359/4279). – jfs May 01 '16 at 17:19
  • @tokland I think I was mistaken. It appeared as though it was ending without reading all stdout, but really there was stderr and the whole process terminated with errors which is why eof was reached – searchengine27 Nov 01 '16 at 18:28
  • 1
    It works great, however if it calls the python script as the child process and with time.sleep(). The iteration of line will block. – binzhang Nov 03 '16 at 07:54
  • @binzhang: Can you upload somewhere that script you're running to check it? – tokland Nov 03 '16 at 14:58
  • @tokland child_process.py: ``` import time import sys while True: print 'hello_world' time.sleep(1) sys.stdout.flush() ``` In the above calling process: ``` for path in execute(["python", "child_thread.py"]): print(path, end="") ``` – binzhang Nov 06 '16 at 12:55
  • @tokland if without sys.stdout.flush(), the iteration of line will block. if without sys.stdout.flush() and time.sleep, the iteration will not block – binzhang Nov 06 '16 at 13:02
  • 7
    @binzhang That's not an error, stdout is buffered by default on Python scripts (also for many Unix tools). Try `execute(["python", "-u", "child_thread.py"])`. More info: http://stackoverflow.com/questions/14258500/python-significance-of-u-option – tokland Nov 06 '16 at 20:18
  • 1
    @binzhang: related: [Python C program subprocess hangs at “for line in iter”](http://stackoverflow.com/q/20503671/4279) (useful if there is no `-u`, grep's `--line-buffered` analogs in a general case) – jfs Nov 07 '16 at 12:06
  • Could you explain the second argument to print() `end=""`? That is treated as a syntax error for me when I try to run it python 3.5, and I can't figure out what it's for. – AnalogWeapon Dec 26 '16 at 05:12
  • @AnalogWeapon I'm confused. That should work on 3.5. Seed the docs for more info: https://docs.python.org/3/library/functions.html#print – tokland Dec 26 '16 at 21:37
  • 2
    You should also set `stderr=subprocess.STDOUT` when you construct Popen to make sure you don't miss out on any error messages – tvt173 Feb 23 '17 at 01:08
  • @tvt173 Sometimes you need to capture stderr, sometimes it's ok to show it on the terminal. Anyway, not sure what would be the best way to read from two both at the same time, @J.F.Sebastian? – tokland Feb 23 '17 at 08:35
  • @tokland, really sleek code. I had to do quite a bit of reading about 'yield' and iterables after seeing your code, but I still can't really put the pieces together. Could you explain in more detail how your code works, and also what was your intuition for using yield in this case? – user43389 Jul 05 '17 at 15:10
  • This does not work with the `scp` command. Trying to send a file and I'm getting no output, but it seems to work for the `tree` command. – Klik Nov 09 '17 at 07:33
  • @Klik: I think that's because `scp` autodetects if there is a terminal or not. There is info in SO, for example: https://stackoverflow.com/questions/3890809/bash-stdout-redirect-of-commands-like-scp – tokland Nov 09 '17 at 11:56
  • @tokland thanks for the reference. I have a working implementation by using rsync. https://askubuntu.com/questions/44059/progress-bar-for-scp-command Doing this I was able to get a progress update of the upload – Klik Nov 09 '17 at 17:14
  • 1
    @tokland, Trying to run this with a while loop in the child process, which works great. But if I put a sleep.time(1) in, so the loop only runs once per second, I get nothing printed until the process finishes. Any thoughts? – Daishozen Feb 28 '19 at 19:45
  • 1
    This does not work for me when my command contains a "&" (ie - trying to run two commands together). Is there any workaround to allow the "&" sign? – maurera Jan 24 '20 at 18:38
  • How to set a timeout for it? – I Wonder Jun 28 '20 at 04:24
  • You can find another solution here https://stackoverflow.com/a/61188550/5664246 – Joy Jedidja Ndjama Jul 29 '20 at 23:35
  • 1
    This is a great solution! Thank you! – QuirkyBit Nov 08 '20 at 17:51
  • I'm puzzled...Does this not still suffer from the fundamental problem of potential blocking on the pipe in case the process sends a lot of data without any newlines? That is, the exact reason why the Python documentation advises to use `communicate()` instead of directly reading from the pipes? – Ton van den Heuvel Apr 26 '21 at 13:43
88

Ok i managed to solve it without threads (any suggestions why using threads would be better are appreciated) by using a snippet from this question Intercepting stdout of a subprocess while it is running

def execute(command):
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    # Poll process for new output until finished
    while True:
        nextline = process.stdout.readline()
        if nextline == '' and process.poll() is not None:
            break
        sys.stdout.write(nextline)
        sys.stdout.flush()

    output = process.communicate()[0]
    exitCode = process.returncode

    if (exitCode == 0):
        return output
    else:
        raise ProcessException(command, exitCode, output)
Community
  • 1
  • 1
Wolkenarchitekt
  • 17,351
  • 28
  • 102
  • 166
  • 3
    Merging the ifischer's and tokland's code works quite well (I had to change `print line,` to `sys.stdout.write(nextline); sys.stdout.flush()`. Otherwise, it would print out every two lines. Then again, this is using IPython's Notebook interface, so maybe something else was happening - regardless, explicitly calling `flush()` works. – eacousineau Oct 14 '12 at 03:17
  • 3
    mister you're my life saver!! really strange that this kind of things are not build-in in library itself.. cause if I write cliapp, i want to show everything what's processing in loop instantly.. s'rsly.. – holms Mar 23 '13 at 01:05
  • 3
    Can this solution be modified to constantly print *both* output and errors? If I change `stderr=subprocess.STDOUT` to `stderr=subprocess.PIPE` and then call `process.stderr.readline()` from within the loop, I seem to run afoul of the very deadlock that is warned about in the documentation for the `subprocess` module. – davidrmcharles Dec 16 '13 at 20:04
  • 8
    @DavidCharles I think what you're looking for is `stdout=subprocess.PIPE,stderr=subprocess.STDOUT` this captures stderr, and I believe (but I've not tested) that it also captures stdin. – Andrew Martin May 13 '14 at 18:11
  • thanks for waiting for exit code. Didn't know how to work it out – Vitaly Isaev May 19 '15 at 16:16
  • @VitalyIsaev: there is [no need to poll the exit status in the loop, you can do it after the loop using `rc = process.wait()`](http://stackoverflow.com/a/17698359/4279) – jfs Apr 26 '16 at 18:54
  • Hi all, got a small doubt, we are breaking at process.Poll() is not None, means each time it poll for the output and it will become None ? – Lokesh Sanapalli Jul 20 '16 at 15:51
  • We are using stdout = subprocess.pipe and stderr = subprocess.STDOUT like this, can anyone give me a good reference to study about these , what will happen using different combinations of these, for instance, putting stdout = subprocess.STDOUT, stderr = subprocess.pipe etc... – Lokesh Sanapalli Jul 20 '16 at 15:56
  • `execute("ls")` runs forever and plot `''` all the time. I ran this command on python3 and I needed to add str() there `nextline = str(process.stdout.readline())`. Any idea why it doesn't want to quit? – F1sher Feb 17 '17 at 13:20
  • @F1sher: readline is probably returning `b""` instead of the `""` used in the code above. Try using `if nextline == b"" and...` – drootang Feb 22 '17 at 21:25
  • the problem with this is that it blocks while waiting for output, making it so you cannot do work (like look for timeout or w/e) until the program outputs another line on stdout/stderr. For a full solution you can use threading/queues a la https://stackoverflow.com/questions/375427/non-blocking-read-on-a-subprocess-pipe-in-python – John Allard Jul 28 '18 at 01:43
  • How does this work without creating scroll bars and identical output? It seems to just stamp and update the screen, yet if I choose which lines to yield, then it begins to scroll with the selected lines over and over again. If I let it run, then it just "updates" the existing screen. Any insight would be greatly appreciated. – lux Aug 17 '18 at 05:38
  • For programs with extremely fast output this methodology was not printing all of the lines. It appears that if a program can produce significantly fast output, such that multiple lines are printed before the next call to readline then those lines will be dropped. – nikc Aug 21 '18 at 01:53
84

To print subprocess' output line-by-line as soon as its stdout buffer is flushed in Python 3:

from subprocess import Popen, PIPE, CalledProcessError

with Popen(cmd, stdout=PIPE, bufsize=1, universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='') # process line here

if p.returncode != 0:
    raise CalledProcessError(p.returncode, p.args)

Notice: you do not need p.poll() -- the loop ends when eof is reached. And you do not need iter(p.stdout.readline, '') -- the read-ahead bug is fixed in Python 3.

See also, Python: read streaming input from subprocess.communicate().

Community
  • 1
  • 1
jfs
  • 346,887
  • 152
  • 868
  • 1,518
  • 3
    This solution worked for me. The accepted solution given above just went on printing blank lines for me. – Codename Nov 26 '15 at 04:59
  • 3
    I did have to add sys.stdout.flush() to get prints immediately. – Codename Nov 26 '15 at 05:07
  • 3
    @Codename: you shouldn't need `sys.stdout.flush()` in the parent -- stdout is line-buffered if it is not redirected to a file/pipe and therefore printing `line` flushes the buffer automatically. You don't need `sys.stdout.flush()` in the child too -- pass `-u` command-line option instead. – jfs Nov 26 '15 at 05:24
  • @ J.F. Sebastian Sorry, I should have mentioned that I am redirecting the output to a file. – Codename Nov 27 '15 at 04:41
  • @Codename: if it is redirected to a file then why do you need `sys.stdout.flush()`? Are you monitoring the file with `tail -f`? Have you considered `check_call(cmd, stdout=file_object)` instead? – jfs Nov 27 '15 at 05:14
  • @ J.F. Sebastian - yes I wanted to have the ability to do a tail on the file. Also, I want to be able to dump the output via command line in the usual way with the > operator and not have a coded filename. – Codename Nov 27 '15 at 08:18
  • 1
    @Codename: if you want to use `>` then run `python -u your-script.py > some-file`. Notice: `-u` option that I've mentioned above (no need to use `sys.stdout.flush()`). – jfs Nov 27 '15 at 09:53
  • I have a couple of constraints. Not everyone using my script will have python command aliased to python 3 binary and I want to keep it as simple as possible for them. Does sys.stdout.flush() have any major disadvantages that I should be concerned of? – Codename Nov 27 '15 at 10:28
  • @Codename: Sprinkling your code with sys.stdout.flush() may affect performance and it is error-prone. I don't see how `-u` is related to `python` command being aliased to whatever. – jfs Nov 27 '15 at 11:23
  • Yes, you are right about the -u. But that would mean one more arg that all the users would have to add while running, right... – Codename Nov 27 '15 at 13:39
  • @Codename: no, it does not mean that. I've answered the question as stated. What to do to accommodate additional requirements depends on specifics. If you want unbuffered output in all cases; [replace `sys.stdout` with an unbuffered object](http://stackoverflow.com/q/107705/4279) or [redirect it](http://stackoverflow.com/q/4675728/4279). To avoid modifying the code, you could create a shell script that set appropriate command-line parameters, environment variables for python executable. As a quick and dirty hack, you could pass `flush=True` to the `print()` function. – jfs Dec 10 '15 at 16:10
  • For my case (Python-based build scripts running under Jenkins) this happened to be the best answer at this page. But I think it is worth adding code for obtaining return code at the end - `return_code = p.wait()` – mvidelgauz Jan 14 '17 at 08:58
  • 1
    @mvidelgauz no need to call `p.wait()`—it is called on exit from the `with` block. Use `p.returncode`. – jfs Jan 14 '17 at 10:02
  • 1
    @J.F.Sebastian In my case `p.returncode` gave `None` - thus my comment :) (Python 3.6 on Windows and 3.4 on Ubuntu) – mvidelgauz Jan 15 '17 at 08:21
  • 1
    @mvidelgauz: It can't be `None` outside the `with`-statement unless an exception occured. Copy-paste the code as is. The indentation of the code is significant in Python – jfs Jan 15 '17 at 12:14
  • @J.F.Sebastian Thanks, I didn't realize that 'p' exists outside of `with`, i added it after `for` but inside `with` – mvidelgauz Jan 15 '17 at 13:11
  • doesn't work for me, I'm trying to capture `bully` output on Kali Linux – vault Feb 02 '21 at 13:32
  • @vault “doesn’t work” is too generic. What did you expect to happen and what happens instead? Do you want capture stderr? Does the utility changes its output if it is redirected to a pipe? (stdout is not tty . You can check by appending “| cat” to the command in your terminal. – jfs Feb 02 '21 at 15:26
  • @jfs here are more details based on [this code](https://pastebin.com/R9wYpYPR). `test1` and `test2` do not print anything during command execution. I waited 1 minute. `test3`, `test4`, and `test5` cause `bully` to print the usage and exit. – vault Feb 03 '21 at 16:31
  • @vault test3-5 are broken (a list arg is wrong with shell=True and I meant you to run the "| cat" variant manually in the terminal, to see the expected output). There are not enough details to know whether test1-2 are broken too (start with passing the command as a literal list, to make sure the arg are split as you expect). Try running your own script (that prints to stdout, stderr) as an exercise. – jfs Feb 03 '21 at 18:33
  • @jfs [These tests](https://pastebin.com/T7hqnMj0) are now all printing nothing. The `| cat` variant doesn't produce any output as you probably expected. The `sys.stdout.write` variant provided in another answer prints something in realtime (but I had problems printing bytes). – vault Feb 03 '21 at 22:40
  • @vault: if running manually the command with `| cat` from the command line doesn't produce any output then the python code with `stdout=PIPE` shouldn't produce any output too. [`sys.stdout.write()`](https://stackoverflow.com/a/4418193/4279) answer shouldn't work at all if you use Python 3 (as my answer assumes) -- you should get TypeError. Try: `cmd = [sys.executable, '-c', "import sys, time\nfor i in range(5):\n print(i, flush=True)\n print(i*i, file=sys.stderr)\n time.sleep(1)"]` – jfs Feb 04 '21 at 16:49
  • @jfs two numbers each second, 10 lines: `0 0 1 1 4 2 9 3 16 4` – vault Feb 05 '21 at 17:45
  • @vault [it is the expected behavior](https://repl.it/@zed1/process-command-output-while-it-is-running#main.py) – jfs Feb 06 '21 at 08:15
  • @jfs is there a way with Popen to retrieve the output line by line, with such commands? – vault Feb 07 '21 at 12:46
  • @vault yes, just follow the links – jfs Feb 07 '21 at 14:39
17

There is actually a really simple way to do this when you just want to print the output:

import subprocess
import sys

def execute(command):
    subprocess.check_call(command, stdout=sys.stdout, stderr=subprocess.STDOUT)

Here we're simply pointing the subprocess to our own stdout, and using existing succeed or exception api.

Neuron
  • 3,776
  • 3
  • 24
  • 44
Andrew Ring
  • 2,430
  • 1
  • 14
  • 25
  • 1
    This solution is simpler and cleaner than @tokland's solution, for Python 3.6. I noticed that the shell=True argument is not necessary. – Good Will Feb 23 '20 at 23:24
  • Good catch, Good Will. Removed `shell=True` – Andrew Ring Feb 25 '20 at 01:54
  • Very astucious, and works perfectly with little code. Maybe you should redirect the subprocess stderr to sys.stderr too ? – Manu May 15 '20 at 19:12
  • Manu you certainly can. I didn't, here, because the attempt in the question was redirecting stderr to stdout. – Andrew Ring May 16 '20 at 21:47
  • Can you explain whats the difference between sys.stdout and subprocess.STDOUT? – Ron Serruya Jun 04 '20 at 08:36
  • Sure, @RonSerruya. [sys.stdout](https://docs.python.org/3/library/sys.html#sys.stdout) is a File object which allows normal writing operations. [subprocess.STDOUT](https://docs.python.org/3/library/subprocess.html#subprocess.STDOUT) is a special value explicitly used to redirect stderr to the same output as stdout. Conceptually, you're saying that you want both feeds to go to the same place, rather than passing in the same value twice. – Andrew Ring Jun 05 '20 at 18:47
  • THANK YOU! I've been browsing all of the stackoverflow subprocess questions and this is the only thing that worked. – Julian Ferry Aug 18 '20 at 18:58
9

@tokland

tried your code and corrected it for 3.4 and windows dir.cmd is a simple dir command, saved as cmd-file

import subprocess
c = "dir.cmd"

def execute(command):
    popen = subprocess.Popen(command, stdout=subprocess.PIPE,bufsize=1)
    lines_iterator = iter(popen.stdout.readline, b"")
    while popen.poll() is None:
        for line in lines_iterator:
            nline = line.rstrip()
            print(nline.decode("latin"), end = "\r\n",flush =True) # yield line

execute(c)
Markus Amalthea Magnuson
  • 7,701
  • 4
  • 38
  • 49
user3759376
  • 91
  • 1
  • 1
  • 4
    you could [simplify your code](http://stackoverflow.com/a/28319191/4279). `iter()` and `end='\r\n'` are unnecessary. Python uses universal newlines mode by default i.e., any `'\n'` is translated to `'\r\n'` during printing. `'latin'` is probably a wrong encoding, you could use `universal_newlines=True` to get text output in Python 3 (decoded using locale's preferred encoding). Don't stop on `.poll()`, there could be buffered unread data. If Python script is running in a console then its output is line-buffered; you can force line-buffering using `-u` option -- you don't need `flush=True` here. – jfs Feb 04 '15 at 10:40
5

For anyone trying the answers to this question to get the stdout from a Python script note that Python buffers its stdout, and therefore it may take a while to see the stdout.

This can be rectified by adding the following after each stdout write in the target script:

sys.stdout.flush()
user1379351
  • 643
  • 1
  • 5
  • 15
  • 1
    But running Python as a subprocess of Python is crazy in the first place. Your script should simply `import` the other script; look into `multiprocessing` or `threading` if you need parallelized execution. – tripleee Jul 13 '18 at 13:57
  • 3
    @triplee There are several scenarios in which running Python as a subprocess of Python is appropriate. I have a number of python batch scripts that I wish to run sequentially, daily. These can be orchestrated by a master Python script that initiates the execution, and emails me if the child script fails. Each script is sandboxed from the other - no naming conflicts. I'm not parallelising so multiprocessing and threading aren't relevant. – user1379351 Jul 21 '18 at 13:34
  • You could also start the other python program using a different python executable than the on that the main python program is running in e.g., `subprocess.run("/path/to/python/executable", "pythonProgramToRun.py")` – Kyle Bridenstine Aug 20 '19 at 18:46
  • You can also use PYTHON_UNBUFFERED env var or launch python with -u to avoid this behavior – Jean-Bernard Jansen Sep 28 '20 at 22:14
  • @tripleee what if the other Python script is executed on another machine? – topkek Mar 24 '21 at 08:51
  • Then you are not running Python as a direct subprocess of Python. Having said that, there _are_ situations where it is useful or even mandatory to run Python as a subprocess of itself (for example, if the subprocess needs to catch signals independently of the parent) but I don't think this is one of them. – tripleee Mar 24 '21 at 08:55
5

In case someone wants to read from both stdout and stderr at the same time using threads, this is what I came up with:

import threading
import subprocess
import Queue

class AsyncLineReader(threading.Thread):
    def __init__(self, fd, outputQueue):
        threading.Thread.__init__(self)

        assert isinstance(outputQueue, Queue.Queue)
        assert callable(fd.readline)

        self.fd = fd
        self.outputQueue = outputQueue

    def run(self):
        map(self.outputQueue.put, iter(self.fd.readline, ''))

    def eof(self):
        return not self.is_alive() and self.outputQueue.empty()

    @classmethod
    def getForFd(cls, fd, start=True):
        queue = Queue.Queue()
        reader = cls(fd, queue)

        if start:
            reader.start()

        return reader, queue


process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdoutReader, stdoutQueue) = AsyncLineReader.getForFd(process.stdout)
(stderrReader, stderrQueue) = AsyncLineReader.getForFd(process.stderr)

# Keep checking queues until there is no more output.
while not stdoutReader.eof() or not stderrReader.eof():
   # Process all available lines from the stdout Queue.
   while not stdoutQueue.empty():
       line = stdoutQueue.get()
       print 'Received stdout: ' + repr(line)

       # Do stuff with stdout line.

   # Process all available lines from the stderr Queue.
   while not stderrQueue.empty():
       line = stderrQueue.get()
       print 'Received stderr: ' + repr(line)

       # Do stuff with stderr line.

   # Sleep for a short time to avoid excessive CPU use while waiting for data.
   sleep(0.05)

print "Waiting for async readers to finish..."
stdoutReader.join()
stderrReader.join()

# Close subprocess' file descriptors.
process.stdout.close()
process.stderr.close()

print "Waiting for process to exit..."
returnCode = process.wait()

if returnCode != 0:
   raise subprocess.CalledProcessError(returnCode, command)

I just wanted to share this, as I ended up on this question trying to do something similar, but none of the answers solved my problem. Hopefully it helps someone!

Note that in my use case, an external process kills the process that we Popen().

Will
  • 21,498
  • 11
  • 84
  • 98
  • 1
    I've had to use something almost exactly like this for python2. While something like this should have been provided in python2, it is not so something like this is absolutely fine. – Stuart Axon Nov 29 '16 at 15:49
5

In Python >= 3.5 using subprocess.run works for me:

import subprocess

cmd = 'echo foo; sleep 1; echo foo; sleep 2; echo foo'
subprocess.run(cmd, shell=True)

(getting the output during execution also works without shell=True) https://docs.python.org/3/library/subprocess.html#subprocess.run

user7017793
  • 83
  • 1
  • 2
  • 2
    This is not "during execution". The `subprocess.run()` call only returns when the subprocess has finished running. – tripleee Apr 25 '19 at 04:19
  • 1
    Can you explain how it is not "during execution"? Something like `>>> import subprocess; subprocess.run('top')` also seems to print "during execution" (and top never finishes). Maybe I'm not grasping some subtle difference? – user7017793 Apr 25 '19 at 18:40
  • If you redirect the output back to Python e.g. with `stdout=subprocess.PIPE` you can only read it after `top` finishes. Your Python program is blocked during the execution of the subprocess. – tripleee Apr 25 '19 at 18:55
  • 1
    Right, that makes sense. The `run` method still works if you're only interested in *seeing* the output as it's generated. If you want to do something with the output in python asynchronously you are right that it doesn't work. – user7017793 Apr 25 '19 at 19:35
4

To answer the original question, the best way IMO is just redirecting subprocess stdout directly to your program's stdout (optionally, the same can be done for stderr, as in example below)

p = Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
p.communicate()
Alleo
  • 6,116
  • 2
  • 32
  • 29
  • 4
    Not specifying anything for `stdout` and `stderr` does the same thing with less code. Though I suppose *explicit is better than implicit.* – tripleee Apr 25 '19 at 04:19
1

This PoC constantly reads the output from a process and can be accessed when needed. Only the last result is kept, all other output is discarded, hence prevents the PIPE from growing out of memory:

import subprocess
import time
import threading
import Queue


class FlushPipe(object):
    def __init__(self):
        self.command = ['python', './print_date.py']
        self.process = None
        self.process_output = Queue.LifoQueue(0)
        self.capture_output = threading.Thread(target=self.output_reader)

    def output_reader(self):
        for line in iter(self.process.stdout.readline, b''):
            self.process_output.put_nowait(line)

    def start_process(self):
        self.process = subprocess.Popen(self.command,
                                        stdout=subprocess.PIPE)
        self.capture_output.start()

    def get_output_for_processing(self):
        line = self.process_output.get()
        print ">>>" + line


if __name__ == "__main__":
    flush_pipe = FlushPipe()
    flush_pipe.start_process()

    now = time.time()
    while time.time() - now < 10:
        flush_pipe.get_output_for_processing()
        time.sleep(2.5)

    flush_pipe.capture_output.join(timeout=0.001)
    flush_pipe.process.kill()

print_date.py

#!/usr/bin/env python
import time

if __name__ == "__main__":
    while True:
        print str(time.time())
        time.sleep(0.01)

output: You can clearly see that there is only output from ~2.5s interval nothing in between.

>>>1520535158.51
>>>1520535161.01
>>>1520535163.51
>>>1520535166.01
0

This works at least in Python3.4

import subprocess

process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE)
for line in process.stdout:
    print(line.decode().strip())
arod
  • 11,893
  • 5
  • 25
  • 33
0

None of the answers here addressed all of my needs.

  1. No threads for stdout (no Queues, etc, either)
  2. Non-blocking as I need to check for other things going on
  3. Use PIPE as I needed to do multiple things, e.g. stream output, write to a log file and return a string copy of the output.

A little background: I am using a ThreadPoolExecutor to manage a pool of threads, each launching a subprocess and running them concurrency. (In Python2.7, but this should work in newer 3.x as well). I don't want to use threads just for output gathering as I want as many available as possible for other things (a pool of 20 processes would be using 40 threads just to run; 1 for the process thread and 1 for stdout...and more if you want stderr I guess)

I'm stripping back a lot of exception and such here so this is based on code that works in production. Hopefully I didn't ruin it in the copy and paste. Also, feedback very much welcome!

import time
import fcntl
import subprocess
import time

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

# Make stdout non-blocking when using read/readline
proc_stdout = proc.stdout
fl = fcntl.fcntl(proc_stdout, fcntl.F_GETFL)
fcntl.fcntl(proc_stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)

def handle_stdout(proc_stream, my_buffer, echo_streams=True, log_file=None):
    """A little inline function to handle the stdout business. """
    # fcntl makes readline non-blocking so it raises an IOError when empty
    try:
        for s in iter(proc_stream.readline, ''):   # replace '' with b'' for Python 3
            my_buffer.append(s)

            if echo_streams:
                sys.stdout.write(s)

            if log_file:
                log_file.write(s)
    except IOError:
        pass

# The main loop while subprocess is running
stdout_parts = []
while proc.poll() is None:
    handle_stdout(proc_stdout, stdout_parts)

    # ...Check for other things here...
    # For example, check a multiprocessor.Value('b') to proc.kill()

    time.sleep(0.01)

# Not sure if this is needed, but run it again just to be sure we got it all?
handle_stdout(proc_stdout, stdout_parts)

stdout_str = "".join(stdout_parts)  # Just to demo

I'm sure there is overhead being added here but it is not a concern in my case. Functionally it does what I need. The only thing I haven't solved is why this works perfectly for log messages but I see some print messages show up later and all at once.

Rafe
  • 1,503
  • 17
  • 30
-3

In Python 3.6 I used this:

import subprocess

cmd = "command"
output = subprocess.call(cmd, shell=True)
print(process)
Rajiv Sharma
  • 5,330
  • 44
  • 44
  • 1
    This is not an answer to this particular question. Waiting for the subprocess to finish before obtaining its output is specifically and precisely what the OP is trying to avoid. The old legacy function `subprocess.call()` has some warts which are fixed by newer functions; in Python 3.6 you would generally use `subprocess.run()` for this; for convenience, the older wrapper function `subprocess.check_output()` is also still available - it returns the actual output from the process (this code would return the exit code only, but even then print something undefined instead). – tripleee Apr 25 '19 at 04:17