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.
This commit is contained in:
Serhiy Storchaka 2013-04-22 20:20:54 +03:00
parent 1859fe80c4
commit fcd9f22238
4 changed files with 78 additions and 10 deletions

View file

@ -116,7 +116,7 @@ use cases, the underlying :class:`Popen` interface can be used directly.
*timeout* was added. *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. 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 :ref:`frequently-used-arguments` (hence the use of keyword-only notation
in the abbreviated signature). The full function signature is largely the in the abbreviated signature). The full function signature is largely the
same as that of the :class:`Popen` constructor - this functions passes all same as that of the :class:`Popen` constructor - this functions passes all
supplied arguments other than *timeout* directly through to that interface. supplied arguments other than *input* and *timeout* directly through to
In addition, *stdout* is not permitted as an argument, as it is used that interface. In addition, *stdout* is not permitted as an argument, as
internally to collect the output from the subprocess. it is used internally to collect the output from the subprocess.
The *timeout* argument is passed to :meth:`Popen.wait`. If the timeout 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 expires, the child process will be killed and then waited for again. The
:exc:`TimeoutExpired` exception will be re-raised after the child process :exc:`TimeoutExpired` exception will be re-raised after the child process
has terminated. 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:: Examples::
>>> subprocess.check_output(["echo", "Hello World!"]) >>> 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) >>> subprocess.check_output(["echo", "Hello World!"], universal_newlines=True)
'Hello World!\n' '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) >>> subprocess.check_output("exit 1", shell=True)
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -167,10 +177,6 @@ use cases, the underlying :class:`Popen` interface can be used directly.
... shell=True) ... shell=True)
'ls: non_existent_file: No such file or directory\n' 'ls: non_existent_file: No such file or directory\n'
.. versionadded:: 3.1
..
.. warning:: .. warning::
Invoking the system shell with ``shell=True`` can be a security hazard 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 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. generates enough output to the pipe to fill up the OS pipe buffer.
.. versionadded:: 3.1
.. versionchanged:: 3.3 .. versionchanged:: 3.3
*timeout* was added. *timeout* was added.
.. versionchanged:: 3.4
*input* was added.
.. data:: DEVNULL .. data:: DEVNULL

View file

@ -175,6 +175,9 @@ check_output(*popenargs, **kwargs):
>>> output = subprocess.check_output(["ls", "-l", "/dev/null"]) >>> 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
---------- ----------
@ -563,14 +566,31 @@ def check_output(*popenargs, timeout=None, **kwargs):
... stderr=STDOUT) ... stderr=STDOUT)
b'ls: non_existent_file: No such file or directory\n' 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 If universal_newlines=True is passed, the return value will be a
string rather than bytes. string rather than bytes.
""" """
if 'stdout' in kwargs: if 'stdout' in kwargs:
raise ValueError('stdout argument not allowed, it will be overridden.') 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: with Popen(*popenargs, stdout=PIPE, **kwargs) as process:
try: try:
output, unused_err = process.communicate(timeout=timeout) output, unused_err = process.communicate(inputdata, timeout=timeout)
except TimeoutExpired: except TimeoutExpired:
process.kill() process.kill()
output, unused_err = process.communicate() output, unused_err = process.communicate()

View file

@ -158,8 +158,28 @@ class ProcessTestCase(BaseTestCase):
stderr=subprocess.STDOUT) stderr=subprocess.STDOUT)
self.assertIn(b'BDFL', output) 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): 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: with self.assertRaises(ValueError) as c:
output = subprocess.check_output( output = subprocess.check_output(
[sys.executable, "-c", "print('will not be run')"], [sys.executable, "-c", "print('will not be run')"],
@ -167,6 +187,20 @@ class ProcessTestCase(BaseTestCase):
self.fail("Expected ValueError when stdout arg supplied.") self.fail("Expected ValueError when stdout arg supplied.")
self.assertIn('stdout', c.exception.args[0]) 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): def test_check_output_timeout(self):
# check_output() function with timeout arg # check_output() function with timeout arg
with self.assertRaises(subprocess.TimeoutExpired) as c: with self.assertRaises(subprocess.TimeoutExpired) as c:

View file

@ -49,6 +49,10 @@ Core and Builtins
Library 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 - Issue #17795: Reverted backwards-incompatible change in SysLogHandler with
Unix domain sockets. Unix domain sockets.