Search code examples
pythongitsubprocessstdoutstderr

Can't get stdout/stderr from (Python) subprocess.check_output()


I'm trying to get the message from a git add command, to print to a log file later on.

import subprocess
import os

filename = 'test.txt'

# Add changes
add_cmd = """git add "%s" """ % filename
os.system(add_cmd)
a = subprocess.check_output(add_cmd, shell=True, stderr=subprocess.STDOUT)

The os.system() call shows in screen:

fatal: Not a git repository (or any of the parent directories): .git

which is correct, since this folder is not a git repo.

But the subprocess.check_output() call fails with:

  File "test.py", line 11, in <module>
    a = subprocess.check_output(add_cmd, shell=True, stderr=subprocess.STDOUT)
  File "/usr/lib/python2.7/subprocess.py", line 573, in check_output
    raise CalledProcessError(retcode, cmd, output=output)
subprocess.CalledProcessError: Command 'git add "test.txt" ' returned non-zero exit status 128

Why am I not able to catch the error message with subprocess.check_output()?


Solution

  • From the documenation for subprocess.check_output():

    If the return code was non-zero it raises a CalledProcessError. The CalledProcessError object will have the return code in the returncode attribute and any output in the output attribute.

    git add returns a non-zero exit code when there is an error condition. Catch that exception, your output is there:

    try:
        a = subprocess.check_output(add_cmd, shell=True, stderr=subprocess.STDOUT)
    except subprocess.CalledProcessError as cpe:
        print cpe.output
    

    Demo:

    >>> import subprocess
    >>> import os
    >>> filename = 'test.txt'
    >>> add_cmd = """git add "%s" """ % filename
    >>> try:
    ...     a = subprocess.check_output(add_cmd, shell=True, stderr=subprocess.STDOUT)
    ... except subprocess.CalledProcessError as cpe:
    ...     print cpe.output
    ...
    fatal: Not a git repository (or any of the parent directories): .git
    
    >>> cpe.returncode
    128
    

    You probably don't need to use shell=True; pass in your arguments as a list instead and they'll be executed without an intermediary shell. This has the added advantage you don't need to worry about properly escaping filename:

    add_cmd = ['git', 'add', filename]
    try:
        a = subprocess.check_output(add_cmd, stderr=subprocess.STDOUT)
    except subprocess.CalledProcessError as cpe:
        print cpe.output