From fcd9f222385c7855241a3f6bfe84b454e2373cdf Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 22 Apr 2013 20:20:54 +0300 Subject: 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. --- Doc/library/subprocess.rst | 26 ++++++++++++++++++-------- Lib/subprocess.py | 22 +++++++++++++++++++++- Lib/test/test_subprocess.py | 36 +++++++++++++++++++++++++++++++++++- Misc/NEWS | 4 ++++ 4 files changed, 78 insertions(+), 10 deletions(-) diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst index 792a58f..70a21eb 100644 --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -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 diff --git a/Lib/subprocess.py b/Lib/subprocess.py index f69b42a..78907e0 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -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() diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 28f6935..6d8afaf 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -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: diff --git a/Misc/NEWS b/Misc/NEWS index 1416cb4..0e1ebac 100644 --- 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. -- cgit v0.12