summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Doc/library/subprocess.rst23
-rw-r--r--Lib/subprocess.py23
-rw-r--r--Lib/test/test_subprocess.py25
-rw-r--r--Misc/NEWS2
4 files changed, 66 insertions, 7 deletions
diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst
index 4a12b76227..88eb7846a3 100644
--- a/Doc/library/subprocess.rst
+++ b/Doc/library/subprocess.rst
@@ -125,12 +125,14 @@ This module defines one class called :class:`Popen`:
*stdin*, *stdout* and *stderr* specify the executed programs' standard input,
standard output and standard error file handles, respectively. Valid values
- are :data:`PIPE`, an existing file descriptor (a positive integer), an
- existing :term:`file object`, and ``None``. :data:`PIPE` indicates that a
- new pipe to the child should be created. With ``None``, no redirection will
- occur; the child's file handles will be inherited from the parent. Additionally,
- *stderr* can be :data:`STDOUT`, which indicates that the stderr data from the
- applications should be captured into the same file handle as for stdout.
+ are :data:`PIPE`, :data:`DEVNULL`, an existing file descriptor (a positive
+ integer), an existing :term:`file object`, and ``None``. :data:`PIPE`
+ indicates that a new pipe to the child should be created. :data:`DEVNULL`
+ indicates that the special file :data:`os.devnull` will be used. With ``None``,
+ no redirection will occur; the child's file handles will be inherited from
+ the parent. Additionally, *stderr* can be :data:`STDOUT`, which indicates
+ that the stderr data from the applications should be captured into the same
+ file handle as for stdout.
If *preexec_fn* is set to a callable object, this object will be called in the
child process just before the child is executed.
@@ -229,6 +231,15 @@ This module defines one class called :class:`Popen`:
Added context manager support.
+.. 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
+
+
.. data:: PIPE
Special value that can be used as the *stdin*, *stdout* or *stderr* argument
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index cc0894544d..0e15f10c66 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -431,7 +431,7 @@ else:
return fds
__all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "getstatusoutput",
- "getoutput", "check_output", "CalledProcessError"]
+ "getoutput", "check_output", "CalledProcessError", "DEVNULL"]
if mswindows:
from _subprocess import CREATE_NEW_CONSOLE, CREATE_NEW_PROCESS_GROUP
@@ -456,6 +456,7 @@ def _cleanup():
PIPE = -1
STDOUT = -2
+DEVNULL = -3
def _eintr_retry_call(func, *args):
@@ -800,6 +801,10 @@ class Popen(object):
# Child is still running, keep us alive until we can wait on it.
_active.append(self)
+ def _get_devnull(self):
+ if not hasattr(self, '_devnull'):
+ self._devnull = os.open(os.devnull, os.O_RDWR)
+ return self._devnull
def communicate(self, input=None, timeout=None):
"""Interact with process: Send data to stdin. Read data from
@@ -889,6 +894,8 @@ class Popen(object):
p2cread, _ = _subprocess.CreatePipe(None, 0)
elif stdin == PIPE:
p2cread, p2cwrite = _subprocess.CreatePipe(None, 0)
+ elif stdin == DEVNULL:
+ p2cread = msvcrt.get_osfhandle(self._get_devnull())
elif isinstance(stdin, int):
p2cread = msvcrt.get_osfhandle(stdin)
else:
@@ -902,6 +909,8 @@ class Popen(object):
_, c2pwrite = _subprocess.CreatePipe(None, 0)
elif stdout == PIPE:
c2pread, c2pwrite = _subprocess.CreatePipe(None, 0)
+ elif stdout == DEVNULL:
+ c2pwrite = msvcrt.get_osfhandle(self._get_devnull())
elif isinstance(stdout, int):
c2pwrite = msvcrt.get_osfhandle(stdout)
else:
@@ -917,6 +926,8 @@ class Popen(object):
errread, errwrite = _subprocess.CreatePipe(None, 0)
elif stderr == STDOUT:
errwrite = c2pwrite
+ elif stderr == DEVNULL:
+ errwrite = msvcrt.get_osfhandle(self._get_devnull())
elif isinstance(stderr, int):
errwrite = msvcrt.get_osfhandle(stderr)
else:
@@ -1026,6 +1037,8 @@ class Popen(object):
c2pwrite.Close()
if errwrite != -1:
errwrite.Close()
+ if hasattr(self, '_devnull'):
+ os.close(self._devnull)
# Retain the process handle, but close the thread handle
self._child_created = True
@@ -1159,6 +1172,8 @@ class Popen(object):
pass
elif stdin == PIPE:
p2cread, p2cwrite = _create_pipe()
+ elif stdin == DEVNULL:
+ p2cread = self._get_devnull()
elif isinstance(stdin, int):
p2cread = stdin
else:
@@ -1169,6 +1184,8 @@ class Popen(object):
pass
elif stdout == PIPE:
c2pread, c2pwrite = _create_pipe()
+ elif stdout == DEVNULL:
+ c2pwrite = self._get_devnull()
elif isinstance(stdout, int):
c2pwrite = stdout
else:
@@ -1181,6 +1198,8 @@ class Popen(object):
errread, errwrite = _create_pipe()
elif stderr == STDOUT:
errwrite = c2pwrite
+ elif stderr == DEVNULL:
+ errwrite = self._get_devnull()
elif isinstance(stderr, int):
errwrite = stderr
else:
@@ -1374,6 +1393,8 @@ class Popen(object):
os.close(c2pwrite)
if errwrite != -1 and errread != -1:
os.close(errwrite)
+ if hasattr(self, '_devnull'):
+ os.close(self._devnull)
# Wait for exec to fail or succeed; possibly raising an
# exception (limited in size)
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index 5f158b9b64..31565432b0 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -323,6 +323,31 @@ class ProcessTestCase(BaseTestCase):
rc = subprocess.call([sys.executable, "-c", cmd], stdout=1)
self.assertEqual(rc, 2)
+ def test_stdout_devnull(self):
+ p = subprocess.Popen([sys.executable, "-c",
+ 'for i in range(10240):'
+ 'print("x" * 1024)'],
+ stdout=subprocess.DEVNULL)
+ p.wait()
+ self.assertEqual(p.stdout, None)
+
+ def test_stderr_devnull(self):
+ p = subprocess.Popen([sys.executable, "-c",
+ 'import sys\n'
+ 'for i in range(10240):'
+ 'sys.stderr.write("x" * 1024)'],
+ stderr=subprocess.DEVNULL)
+ p.wait()
+ self.assertEqual(p.stderr, None)
+
+ def test_stdin_devnull(self):
+ p = subprocess.Popen([sys.executable, "-c",
+ 'import sys;'
+ 'sys.stdin.read(1)'],
+ stdin=subprocess.DEVNULL)
+ p.wait()
+ self.assertEqual(p.stdin, None)
+
def test_cwd(self):
tmpdir = tempfile.gettempdir()
# We cannot use os.path.realpath to canonicalize the path,
diff --git a/Misc/NEWS b/Misc/NEWS
index a5d1bfe4db..e3c04538ef 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -72,6 +72,8 @@ Core and Builtins
Library
-------
+- Issue #5870: Add `subprocess.DEVNULL` constant.
+
- Issue #11133: fix two cases where inspect.getattr_static can trigger code
execution. Patch by Andreas Stührk.