diff options
-rw-r--r-- | Doc/library/subprocess.rst | 24 | ||||
-rw-r--r-- | Lib/subprocess.py | 72 | ||||
-rw-r--r-- | Lib/test/test_subprocess.py | 34 | ||||
-rw-r--r-- | Misc/NEWS | 3 |
4 files changed, 121 insertions, 12 deletions
diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst index 42e50f6b3b..468892a576 100644 --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -149,6 +149,30 @@ This module also defines two shortcut functions: .. versionadded:: 2.5 +.. function:: check_call_output(*popenargs, **kwargs) + + Run command with arguments and return its output as a byte string. + + If the exit code was non-zero it raises a CalledProcessError. The + CalledProcessError object will have the return code in the returncode + attribute and output in the output attribute. + + The arguments are the same as for the Popen constructor. Example: + + >>> subprocess.check_call_output(["ls", "-l", "/dev/null"]) + 'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n' + + The stdout argument is not allowed as it is used internally. + To capture standard error in the result, use stderr=subprocess.STDOUT. + + >>> subprocess.check_call_output( + ["/bin/sh", "-c", "ls non_existant_file ; exit 0"], + stderr=subprocess.STDOUT) + 'ls: non_existant_file: No such file or directory\n' + + .. versionadded:: 2.7 + + Exceptions ^^^^^^^^^^ diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 935827ace9..7caf528b4c 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -107,7 +107,7 @@ appearance of the main window and priority for the new process. (Windows only) -This module also defines two shortcut functions: +This module also defines some shortcut functions: call(*popenargs, **kwargs): Run command with arguments. Wait for command to complete, then @@ -127,6 +127,17 @@ check_call(*popenargs, **kwargs): check_call(["ls", "-l"]) +check_call_output(*popenargs, **kwargs): + Run command with arguments and return its output as a byte string. + + If the exit code was non-zero it raises a CalledProcessError. The + CalledProcessError object will have the return code in the returncode + attribute and output in the output attribute. + + The arguments are the same as for the Popen constructor. Example: + + output = subprocess.check_call_output(["ls", "-l", "/dev/null"]) + Exceptions ---------- Exceptions raised in the child process, before the new program has @@ -141,8 +152,8 @@ should prepare for OSErrors. A ValueError will be raised if Popen is called with invalid arguments. -check_call() will raise CalledProcessError, if the called process -returns a non-zero return code. +check_call() and check_call_output() will raise CalledProcessError, if the +called process returns a non-zero return code. Security @@ -361,12 +372,15 @@ import signal # Exception classes used by this module. class CalledProcessError(Exception): - """This exception is raised when a process run by check_call() returns - a non-zero exit status. The exit status will be stored in the - returncode attribute.""" - def __init__(self, returncode, cmd): + """This exception is raised when a process run by check_call() or + check_call_output() returns a non-zero exit status. + The exit status will be stored in the returncode attribute; + check_call_output() will also store the output in the output attribute. + """ + 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) @@ -403,7 +417,8 @@ else: import fcntl import pickle -__all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "CalledProcessError"] +__all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", + "check_call_output", "CalledProcessError"] try: MAXFD = os.sysconf("SC_OPEN_MAX") @@ -455,12 +470,45 @@ def check_call(*popenargs, **kwargs): check_call(["ls", "-l"]) """ retcode = call(*popenargs, **kwargs) - cmd = kwargs.get("args") - if cmd is None: - cmd = popenargs[0] if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] raise CalledProcessError(retcode, cmd) - return retcode + return 0 + + +def check_call_output(*popenargs, **kwargs): + """Run command with arguments and return its output as a byte string. + + If the exit code was non-zero it raises a CalledProcessError. The + CalledProcessError object will have the return code in the returncode + attribute and output in the output attribute. + + The arguments are the same as for the Popen constructor. Example: + + >>> check_call_output(["ls", "-l", "/dev/null"]) + 'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n' + + The stdout argument is not allowed as it is used internally. + To capture standard error in the result, use stderr=subprocess.STDOUT. + + >>> check_call_output(["/bin/sh", "-c", + "ls -l non_existant_file ; exit 0"], + stderr=subprocess.STDOUT) + 'ls: non_existant_file: No such file or directory\n' + """ + if 'stdout' in kwargs: + raise ValueError('stdout argument not allowed, it will be overridden.') + process = Popen(*popenargs, stdout=PIPE, **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + raise CalledProcessError(retcode, cmd, output=output) + return output def list2cmdline(seq): diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index e7ba26fae1..878b79bd95 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -72,6 +72,40 @@ class ProcessTestCase(unittest.TestCase): else: self.fail("Expected CalledProcessError") + def test_check_call_output(self): + # check_call_output() function with zero return code + output = subprocess.check_call_output( + [sys.executable, "-c", "print 'BDFL'"]) + self.assertTrue('BDFL' in output) + + def test_check_call_output_nonzero(self): + # check_call() function with non-zero return code + try: + subprocess.check_call_output( + [sys.executable, "-c", "import sys; sys.exit(5)"]) + except subprocess.CalledProcessError, e: + self.assertEqual(e.returncode, 5) + else: + self.fail("Expected CalledProcessError") + + def test_check_call_output_stderr(self): + # check_call_output() function stderr redirected to stdout + output = subprocess.check_call_output( + [sys.executable, "-c", "import sys; sys.stderr.write('BDFL')"], + stderr=subprocess.STDOUT) + self.assertTrue('BDFL' in output) + + def test_check_call_output_stdout_arg(self): + # check_call_output() function stderr redirected to stdout + try: + output = subprocess.check_call_output( + [sys.executable, "-c", "print 'will not be run'"], + stdout=sys.stdout) + except ValueError, e: + self.assertTrue('stdout' in e.args[0]) + else: + self.fail("Expected ValueError when stdout arg supplied.") + def test_call_kwargs(self): # call() function with keyword args newenv = os.environ.copy() @@ -60,6 +60,9 @@ Core and Builtins Library ------- +- Added the subprocess.check_call_output() convenience function to get output + from a subprocess on success or raise an exception on error. + - Issue #1055234: cgi.parse_header(): Fixed parsing of header parameters to support unusual filenames (such as those containing semi-colons) in Content-Disposition headers. |