15

I'd like a good method that matches the interface of subprocess.check_call -- ie, it throws CalledProcessError when it fails, is synchronous, &c -- but instead of returning the return code of the command (if it even does that) returns the program's output, either only stdout, or a tuple of (stdout, stderr).

Does somebody have a method that does this?

codeforester
  • 39,467
  • 16
  • 112
  • 140
Chris R
  • 17,546
  • 23
  • 105
  • 172
  • I linked this as a duplicate from a question where the OP tried to capture the output from `os.system`. Briefly, there is no way to do that; `os.system` runs a command completely outside of Python's control, and simply returns its exit code (zero for success, 1-255 for failure). The proper solution is to switch to `subprocess.run()` and friends, where you get this control. (There are several old questions with accepted answers which suggest `os.popen()` but that was the wrong answer for a long time, and now simply an obscure wrapper which calls `subprocess` anyway.) – tripleee Jul 24 '20 at 11:35
  • It is not exact duplicate. `check_call()` does not run shell unless you explicitly ask it. – jfs Jul 24 '20 at 19:58
  • Look into simppl pip module https://stackoverflow.com/a/64381712 – 0x90 Oct 27 '20 at 05:38

4 Answers4

25

Python 2.7+

from subprocess import check_output as qx

Python < 2.7

From subprocess.py:

import subprocess
def check_output(*popenargs, **kwargs):
    if 'stdout' in kwargs:
        raise ValueError('stdout argument not allowed, it will be overridden.')
    process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
    output, unused_err = process.communicate()
    retcode = process.poll()
    if retcode:
        cmd = kwargs.get("args")
        if cmd is None:
            cmd = popenargs[0]
        raise subprocess.CalledProcessError(retcode, cmd, output=output)
    return output

class CalledProcessError(Exception):
    def __init__(self, returncode, cmd, output=None):
        self.returncode = returncode
        self.cmd = cmd
        self.output = output
    def __str__(self):
        return "Command '%s' returned non-zero exit status %d" % (
            self.cmd, self.returncode)
# overwrite CalledProcessError due to `output` keyword might be not available
subprocess.CalledProcessError = CalledProcessError

See also Capturing system command output as a string for another example of possible check_output() implementation.

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • OK, thanks for the information, reverted. I think it would be nice if the example was easy to copy-paste, that's what I was looking for when I found this answer. – Nickolay Jan 24 '11 at 19:00
  • Typically, output for an error will be written to stderr, not stdout. So if you want the error message to be stored in the exception, you'll need to PIPE stderr as well, and pass it to the exception constructor. I've updated the answer. – orodbhen Jul 23 '15 at 14:57
  • @orodbhen: if you need to capture stderr; call `check_output(..., stderr=STDOUT)` or use `Popen().communicate()` (to capture stdout/stderr separately). This answer shows how to get `check_output()` on older Python versions. It is not appropriate to change its API. – jfs Jul 23 '15 at 15:11
  • Maybe update with `subprocess.run(..., check=True, capture_output=True, text=True)` as the recommended replacement for Python 3.5+. – tripleee Jul 24 '20 at 12:51
  • `check_output()` is still the answer if you want an analog of `check_call()` that returns commands’ stdout even in Python 3.5+ (`check_output()` is implemented in terms of `run()` i.e., you can pass `text` and other `run()` parameters if desired. Iff `check_output()` is not enough, then in more complex cases `run()` could be used directly (or even Popen() — in more general case when the convenience functions such as `check_output()`, `run( )` are too limited. – jfs Jul 24 '20 at 19:56
1

I can not get formatting in a comment, so this is in response to J.F. Sebastian's answer

I found this very helpful so I figured I would add to this answer. I wanted to be able to work seamlessly in the code without checking the version. This is what I did...

I put the code above into a file called 'subprocess_compat.py'. Then in the code where I use subprocess I did.

import sys
if sys.version_info < (2, 7):
    import subprocess_compat
    subprocess.check_output = subprocess_compat.check_output

Now anywhere in the code I can just call 'subprocess.check_output' with the params I want and it will work regardless of which version of python I am using.

swill
  • 2,047
  • 1
  • 16
  • 11
  • 4
    you don't need the version check: just catch ImportError in `subprocess_compat` e.g.: `try:\n from subprocess import check_output\nexcept ImportError:\n def check_output(...` and use in your code `from subprocess_compat import check_output` – jfs May 05 '14 at 08:56
1

After I read this twice, I realized it's ten years old and most answers apply to the now deprecated python2.7 rather than python3.

Now that we are - or should be - on python3, it seems that the best option for python >= 3.7 is to use the following as is mentioned in multiple comments:

result = subprocess.run(..., check=True, capture_output=True)

To save you searching for more details, I recommend an answer I found with wonderful detail by SethMMorton in an answer to "How to suppress or capture the output of subprocess.run()?" As described there, you can access stdout, stderr directly as:

print(result.stdout)
print(result.stderr)

If you need to support Python 3.6:

You can however easily "emulate" this by setting both stdout and stderr to PIPE:

from subprocess import PIPE

subprocess.run(["ls", "-l", "/dev/null"], stdout=PIPE, stderr=PIPE)

This info is from Willem Van Onsem's answer to a related question.

I tend to go straight to https://docs.python.org/3/library/subprocess.html to refresh my memory on general subprocess things. (The SO examples are often easier for me to access quickly though.)

sage
  • 4,863
  • 2
  • 44
  • 47
-2

This function returns terminal output in the form of list of string.

import subprocess,sys
def output(cmd):
    cmd_l = cmd.split()
    output = subprocess.Popen(cmd_l, stdout=subprocess.PIPE).communicate()[0]
    output = output.decode("utf-8")
    return (output)
NILESH KUMAR
  • 413
  • 5
  • 10