Camion
Camion

Reputation: 1374

How can I get error messages when I use subprocess with check=True

I just realized that if I use check=True in a subprocess, I don't get the CompletedProcess descriptor. This looks like a design flaw : How can I get the error messages sent by the called program ?

Obviously there are other ways to get the return code of my program, but the point here is that the check=True parameter seems incompatible with error handling.

Or did I miss something ?

#!/usr/bin/python3

import subprocess
import sys

subproc1 = subprocess.run('ls /nonexistent', stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True)
print(subproc1.stderr, file=sys.stderr, end='', flush=True)
print(subproc1.stdout, file=sys.stdout, end='', flush=True)
print('▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔', file=sys.stderr, flush=True)


try: subproc2 = subprocess.run('ls /', stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True, check=True)
except subprocess.CalledProcessError:
    print("FAIL")
    print(subproc2.stderr, file=sys.stderr, end='', flush=True)
    print(subproc2.stdout, file=sys.stdout, end='', flush=True)
    print('▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔', file=sys.stderr, flush=True)
    raise
else:
    print("GOOD")
    print(subproc2.stderr, file=sys.stderr, end='', flush=True)
    print(subproc2.stdout, file=sys.stdout, end='', flush=True)
    print('▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔', file=sys.stderr, flush=True)


try: subproc3 = subprocess.run('ls /nonexistent', stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True, check=True)
except subprocess.CalledProcessError:
    print("FAIL")
    print(subproc3.stderr, file=sys.stderr, end='', flush=True)
    print(subproc3.stdout, file=sys.stdout, end='', flush=True)
    print('▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔', file=sys.stderr, flush=True)
    raise
else:
    print("GOOD")
    print(subproc3.stderr, file=sys.stderr, end='', flush=True)
    print(subproc3.stdout, file=sys.stdout, end='', flush=True)
    print('▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔', file=sys.stderr, flush=True)

yields:

ls: cannot access '/nonexistent': No such file or directory
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
GOOD
bin
[...]
var
vmlinuz
vmlinuz.old
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
FAIL
Traceback (most recent call last):
  File "/root/subprocess_error.py", line 27, in <module>
    try: subproc3 = subprocess.run('ls /nonexistent', stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True, check=True)
  File "/usr/lib/python3.7/subprocess.py", line 487, in run
    output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command 'ls /nonexistent' returned non-zero exit status 2.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/root/subprocess_error.py", line 30, in <module>
    print(subproc3.stderr, file=sys.stderr, end='', flush=True)
NameError: name 'subproc3' is not defined

Upvotes: 0

Views: 364

Answers (1)

Charles Duffy
Charles Duffy

Reputation: 295281

The captured content is saved to the exception itself.

Assign the exception object to a variable, and read .stdout and .stderr off it.

import subprocess
import sys

try:
    subproc3 = subprocess.run('ls /nonexistent', stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True, check=True)
except subprocess.CalledProcessError as ex:
    print("FAIL")
    print(ex.stderr, file=sys.stderr, end='', flush=True)
    print(ex.stdout, file=sys.stdout, end='', flush=True)

Upvotes: 1

Related Questions