diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -111,46 +111,56 @@ use cases, the underlying :class:`Popen` the pipes are not being read in the current process, the child process may block if it generates enough output to a pipe to fill up the OS pipe buffer. .. versionchanged:: 3.3 *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 as a byte string. If the return code was non-zero it raises a :exc:`CalledProcessError`. The :exc:`CalledProcessError` object will have the return code in the :attr:`returncode` attribute and any output in the :attr:`output` attribute. The arguments shown above are merely the most common ones, described below 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!"]) b'Hello World!\n' >>> 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): ... subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1 By default, this function will return the data as encoded bytes. The actual encoding of the output data may depend on the command being invoked, so the decoding to text will often need to be handled at the application level. @@ -162,33 +172,35 @@ use cases, the underlying :class:`Popen` ``stderr=subprocess.STDOUT``:: >>> subprocess.check_output( ... "ls non_existent_file; exit 0", ... stderr=subprocess.STDOUT, ... 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 if combined with untrusted input. See the warning under :ref:`frequently-used-arguments` for details. .. note:: Do not use ``stderr=PIPE`` with this function. As the pipe is not being 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 Special value that can be used as the *stdin*, *stdout* or *stderr* argument to :class:`Popen` and indicates that the special file :data:`os.devnull` will be used. .. versionadded:: 3.3 diff --git a/Lib/subprocess.py b/Lib/subprocess.py --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -171,16 +171,19 @@ check_output(*popenargs, **kwargs): 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_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 ---------- Exceptions raised in the child process, before the new program has started to execute, will be re-raised in the parent. Additionally, the exception object will have one extra attribute called 'child_traceback', which is a string containing traceback information from the childs point of view. @@ -560,22 +563,39 @@ def check_output(*popenargs, timeout=Non The stdout argument is not allowed as it is used internally. To capture standard error in the result, use stderr=STDOUT. >>> check_output(["/bin/sh", "-c", ... "ls -l non_existent_file ; exit 0"], ... 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 '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() raise TimeoutExpired(process.args, timeout, output=output) except: process.kill() process.wait() raise diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -125,25 +125,59 @@ class ProcessTestCase(BaseTestCase): def test_check_output_stderr(self): # check_output() function stderr redirected to stdout output = subprocess.check_output( [sys.executable, "-c", "import sys; sys.stderr.write('BDFL')"], 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')"], stdout=sys.stdout) 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: output = subprocess.check_output( [sys.executable, "-c", "import sys, time\n" "sys.stdout.write('BDFL')\n" "sys.stdout.flush()\n" diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -400,16 +400,20 @@ Library add_argument) only once. Before, the type function was called twice in the case where the default was specified and the argument was given as well. This was especially problematic for the FileType type, as a default file would always be opened, even if a file argument was specified on the command line. - Issue #15906: Fix a regression in argparse caused by the preceding change, when ``action='append'``, ``type='str'`` and ``default=[]``. +- 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. + Extension Modules ----------------- - Issue #16113: Added sha3 module based on the Keccak reference implementation 3.2. The `hashlib` module has four additional hash algorithms: `sha3_224`, `sha3_256`, `sha3_384` and `sha3_512`. As part of the patch some common code was moved from _hashopenssl.c to hashlib.h.