]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Adds a subprocess.check_call_output() function to return the output from a
authorGregory P. Smith <greg@mad-scientist.com>
Thu, 4 Dec 2008 20:21:09 +0000 (20:21 +0000)
committerGregory P. Smith <greg@mad-scientist.com>
Thu, 4 Dec 2008 20:21:09 +0000 (20:21 +0000)
process on success or raise an exception on error.

Doc/library/subprocess.rst
Lib/subprocess.py
Lib/test/test_subprocess.py
Misc/NEWS

index 42e50f6b3b978f8209e10963b9d45e2f67d9b4fb..468892a5763790d2972d5e4518d3f590b13738e5 100644 (file)
@@ -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
 ^^^^^^^^^^
 
index 935827ace901893e7e793f2401d0255e29f26a44..7caf528b4ce50614e1205bc317c95e4467a52e34 100644 (file)
@@ -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):
index e7ba26fae1c3c2ba5257f1ce806fb4a6ab517730..878b79bd95513b8e8d23a916be0c159388a2bada 100644 (file)
@@ -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()
index 300c8191811dda6fb2ca443331789716090a866f..790579554222051bfa298d7de3f0779b3be6446a 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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.