Subprocess
Python >= 3.5: Returncode, errors and standard output
If one uses Python >= 3.5 there is a new run
command which also returns the standard output.
>>> import subprocess
>>> result = subprocess.run(["ls", "-l"], stdout=subprocess.PIPE)
>>> result.stdout.decode('utf-8')
`total 0\n----------- root root 0 Jan 00:01 filename\n'
Returncode (Python < 3.5)
from subprocess import call
retcode = call(["matlab", "-nodesktop", "-no"])
Standard output (Python 2.7 to 3.4)
>>> output = subprocess.check_output(["ls", "-l"])
>>> output.stdout.decode('utf-8')
Mind that there is no stdout=subprocess.PIPE
, but one only can get the standard output and not the error output or return code, like with run
.
Thanks to this stackoverflow post and of cause the Official docu.
Capturing standard output of other's libraries
If you do not write the subprocess-calls yourself, but use a library that calls shell commands and you do not want to patch it, there is also a solution for you.
This solution involves the cStringIO library which is TODO not available for PyPy and dramatically changed from Python 2 to 3, since the string handling completely changed, so this solution is only valid for python 2.
import sys
from cStringIO import StringIO
class capture(list):
def __enter__(self):
self._stdout = sys.stdout
sys.stdout = self._stringio = StringIO()
return self
def __exit__(self, *args):
self.extend(self._stringio.getvalue().splitlines())
del self._stringio
sys.stdout = self._stdout
import contextlib
# Older/alternative version using contextlib
# @contextlib.contextmanager
# def capture():
# import sys
# from cStringIO import StringIO
# print("capture")
# oldout, olderr = sys.stdout, sys.stderr
# print("pre try")
# try:
# out = [StringIO(), StringIO()]
# print("pre out in")
# sys.stdout, sys.stderr = out
# print("pre yield")
# yield out
# print("post yield")
# finally:
# sys.stdout, sys.stderr = oudout, olderr
# out[0] = out[0].getvalue()
# out[1] = out[1].getvalue()
# print("end finally")
If you then want to get the stdout of a library call you have got a context manager. In the following example I get the stdout produced by the pyepics library and if there is there is output an error occurred and I need to run the request again.
def getpv(pv, rerun=0):
_LIMIT = 10
with capture() as out:
data = epics.caget(pv)
if out and rerun < _LIMIT:
print("waiting 1 s..., out={}".format(out))
time.sleep(1)
return getpv(pv)
return data
The important part is the with capture() as out
. See also my other blog post on that topic.