]> granicus.if.org Git - python/commitdiff
Issue #16624: `subprocess.check_output` now accepts an `input` argument,
authorSerhiy Storchaka <storchaka@gmail.com>
Mon, 22 Apr 2013 17:20:54 +0000 (20:20 +0300)
committerSerhiy Storchaka <storchaka@gmail.com>
Mon, 22 Apr 2013 17:20:54 +0000 (20:20 +0300)
allowing the subprocess's stdin to be provided as a (byte) string.
Patch by Zack Weinberg.

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

index 792a58fdd479fd869e94ce6262f33cd107905d6a..70a21ebeada9ef3e563545039d6e21321b546a22 100644 (file)
@@ -116,7 +116,7 @@ use cases, the underlying :class:`Popen` interface can be used directly.
       *timeout* was added.
 
 
-.. function:: check_output(args, *, stdin=None, stderr=None, shell=False, universal_newlines=False, timeout=None)
+.. function:: check_output(args, *, input=None, stdin=None, stderr=None, shell=False, universal_newlines=False, timeout=None)
 
    Run command with arguments and return its output.
 
@@ -129,15 +129,21 @@ use cases, the underlying :class:`Popen` interface can be used directly.
    in :ref:`frequently-used-arguments` (hence the use of keyword-only notation
    in the abbreviated signature). The full function signature is largely the
    same as that of the :class:`Popen` constructor - this functions passes all
-   supplied arguments other than *timeout* directly through to that interface.
-   In addition, *stdout* is not permitted as an argument, as it is used
-   internally to collect the output from the subprocess.
+   supplied arguments other than *input* and *timeout* directly through to
+   that interface.  In addition, *stdout* is not permitted as an argument, as
+   it is used internally to collect the output from the subprocess.
 
    The *timeout* argument is passed to :meth:`Popen.wait`. If the timeout
    expires, the child process will be killed and then waited for again.  The
    :exc:`TimeoutExpired` exception will be re-raised after the child process
    has terminated.
 
+   The *input* argument is passed to :meth:`Popen.communicate` and thus to the
+   subprocess's stdin.  If used it must be a byte sequence, or a string if
+   ``universal_newlines=True``.  When used, the internal :class:`Popen` object
+   is automatically created with ``stdin=PIPE``, and the *stdin* argument may
+   not be used as well.
+
    Examples::
 
       >>> subprocess.check_output(["echo", "Hello World!"])
@@ -146,6 +152,10 @@ use cases, the underlying :class:`Popen` interface can be used directly.
       >>> subprocess.check_output(["echo", "Hello World!"], universal_newlines=True)
       'Hello World!\n'
 
+      >>> subprocess.check_output(["sed", "-e", "s/foo/bar/"],
+      ...                         input=b"when in the course of fooman events\n")
+      b'when in the course of barman events\n'
+
       >>> subprocess.check_output("exit 1", shell=True)
       Traceback (most recent call last):
          ...
@@ -167,10 +177,6 @@ use cases, the underlying :class:`Popen` interface can be used directly.
       ...     shell=True)
       'ls: non_existent_file: No such file or directory\n'
 
-   .. versionadded:: 3.1
-
-   ..
-
    .. warning::
 
       Invoking the system shell with ``shell=True`` can be a security hazard
@@ -183,9 +189,13 @@ use cases, the underlying :class:`Popen` interface can be used directly.
       read in the current process, the child process may block if it
       generates enough output to the pipe to fill up the OS pipe buffer.
 
+   .. versionadded:: 3.1
+
    .. versionchanged:: 3.3
       *timeout* was added.
 
+   .. versionchanged:: 3.4
+      *input* was added.
 
 .. data:: DEVNULL
 
index f69b42a715fddc9eaf5b70945cfac84a182842bd..78907e0e943c9c7db926a8cb7aa77ec4372b2829 100644 (file)
@@ -175,6 +175,9 @@ check_output(*popenargs, **kwargs):
 
     >>> output = subprocess.check_output(["ls", "-l", "/dev/null"])
 
+    There is an additional optional argument, "input", allowing you to
+    pass a string to the subprocess's stdin.  If you use this argument
+    you may not also use the Popen constructor's "stdin" argument.
 
 Exceptions
 ----------
@@ -563,14 +566,31 @@ def check_output(*popenargs, timeout=None, **kwargs):
     ...              stderr=STDOUT)
     b'ls: non_existent_file: No such file or directory\n'
 
+    There is an additional optional argument, "input", allowing you to
+    pass a string to the subprocess's stdin.  If you use this argument
+    you may not also use the Popen constructor's "stdin" argument, as
+    it too will be used internally.  Example:
+
+    >>> check_output(["sed", "-e", "s/foo/bar/"],
+    ...              input=b"when in the course of fooman events\n")
+    b'when in the course of barman events\n'
+
     If universal_newlines=True is passed, the return value will be a
     string rather than bytes.
     """
     if 'stdout' in kwargs:
         raise ValueError('stdout argument not allowed, it will be overridden.')
+    if 'input' in kwargs:
+        if 'stdin' in kwargs:
+            raise ValueError('stdin and input arguments may not both be used.')
+        inputdata = kwargs['input']
+        del kwargs['input']
+        kwargs['stdin'] = PIPE
+    else:
+        inputdata = None
     with Popen(*popenargs, stdout=PIPE, **kwargs) as process:
         try:
-            output, unused_err = process.communicate(timeout=timeout)
+            output, unused_err = process.communicate(inputdata, timeout=timeout)
         except TimeoutExpired:
             process.kill()
             output, unused_err = process.communicate()
index 28f6935ab7952ac7574af81e9be5b2a533f62e0f..6d8afaf69e9d1accceab881bdc1aa73a4fb578df 100644 (file)
@@ -158,8 +158,28 @@ class ProcessTestCase(BaseTestCase):
                 stderr=subprocess.STDOUT)
         self.assertIn(b'BDFL', output)
 
+    def test_check_output_stdin_arg(self):
+        # check_output() can be called with stdin set to a file
+        tf = tempfile.TemporaryFile()
+        self.addCleanup(tf.close)
+        tf.write(b'pear')
+        tf.seek(0)
+        output = subprocess.check_output(
+                [sys.executable, "-c",
+                 "import sys; sys.stdout.write(sys.stdin.read().upper())"],
+                stdin=tf)
+        self.assertIn(b'PEAR', output)
+
+    def test_check_output_input_arg(self):
+        # check_output() can be called with input set to a string
+        output = subprocess.check_output(
+                [sys.executable, "-c",
+                 "import sys; sys.stdout.write(sys.stdin.read().upper())"],
+                input=b'pear')
+        self.assertIn(b'PEAR', output)
+
     def test_check_output_stdout_arg(self):
-        # check_output() function stderr redirected to stdout
+        # check_output() refuses to accept 'stdout' argument
         with self.assertRaises(ValueError) as c:
             output = subprocess.check_output(
                     [sys.executable, "-c", "print('will not be run')"],
@@ -167,6 +187,20 @@ class ProcessTestCase(BaseTestCase):
             self.fail("Expected ValueError when stdout arg supplied.")
         self.assertIn('stdout', c.exception.args[0])
 
+    def test_check_output_stdin_with_input_arg(self):
+        # check_output() refuses to accept 'stdin' with 'input'
+        tf = tempfile.TemporaryFile()
+        self.addCleanup(tf.close)
+        tf.write(b'pear')
+        tf.seek(0)
+        with self.assertRaises(ValueError) as c:
+            output = subprocess.check_output(
+                    [sys.executable, "-c", "print('will not be run')"],
+                    stdin=tf, input=b'hare')
+            self.fail("Expected ValueError when stdin and input args supplied.")
+        self.assertIn('stdin', c.exception.args[0])
+        self.assertIn('input', c.exception.args[0])
+
     def test_check_output_timeout(self):
         # check_output() function with timeout arg
         with self.assertRaises(subprocess.TimeoutExpired) as c:
index 1416cb4aea0ade419ebc51eb028d75beb5415d3b..0e1ebac7714cc7591c82c030499e9392fb9185cc 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -49,6 +49,10 @@ Core and Builtins
 Library
 -------
 
+- Issue #16624: `subprocess.check_output` now accepts an `input` argument,
+  allowing the subprocess's stdin to be provided as a (byte) string.
+  Patch by Zack Weinberg.
+
 - Issue #17795: Reverted backwards-incompatible change in SysLogHandler with
   Unix domain sockets.