diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst index f520d989e0c70d..97547f57071e3b 100644 --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -264,6 +264,20 @@ underlying :class:`Popen` interface can be used directly. *stdout* and *stderr* attributes added +.. function:: shell(cmd, **kwargs) + + Run a shell command. Wait for command to complete, then return a + :class:`CompletedProcess` instance. + + The *cmd* must be a :class:`str`. + + See the :func:`run` function documentation for optional parameters. + + Read the `Security Considerations`_ section before using this function. + + .. versionadded:: 3.14 + + .. _frequently-used-arguments: Frequently Used Arguments @@ -764,6 +778,9 @@ quoted appropriately to avoid vulnerabilities. On :ref:`some platforms `, it is possible to use :func:`shlex.quote` for this escaping. +The functions :func:`shell` and :func:`getstatusoutput` always use +``shell=True``. + On Windows, batch files (:file:`*.bat` or :file:`*.cmd`) may be launched by the operating system in a system shell regardless of the arguments passed to this library. This could result in arguments being parsed according to shell rules, diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 9662044915b8ca..6bce28540c964e 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -110,6 +110,14 @@ pathlib another. (Contributed by Barney Gale in :gh:`73991`.) +subprocess +---------- + +* Add :func:`subprocess.shell` function to run a shell command. + Read the :ref:`Security Considerations ` section before + using this function. + (Contributed by Victor Stinner in :gh:`120952`.) + symtable -------- diff --git a/Lib/subprocess.py b/Lib/subprocess.py index bc08878db313df..85f20a960cd856 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -62,7 +62,7 @@ __all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "getstatusoutput", "getoutput", "check_output", "run", "CalledProcessError", "DEVNULL", - "SubprocessError", "TimeoutExpired", "CompletedProcess"] + "SubprocessError", "TimeoutExpired", "CompletedProcess", "shell"] # NOTE: We intentionally exclude list2cmdline as it is # considered an internal implementation detail. issue10838. @@ -2221,3 +2221,19 @@ def kill(self): """Kill the process with SIGKILL """ self.send_signal(signal.SIGKILL) + + +def shell(cmd, **kwargs): + """ + Run a shell command. + + Read the Security Considerations section of the documentation before using + this function. + """ + if not kwargs.pop('shell', True): + raise ValueError("the 'shell' argument must be True or unspecified") + + if not isinstance(cmd, str): + raise TypeError("cmd type must be str") + + return run(cmd, shell=True, **kwargs) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 8b69cd03ba7f24..69053e96af9e24 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -1824,6 +1824,22 @@ def test_encoding_warning(self): self.assertTrue(lines[0].startswith(b":2: EncodingWarning: ")) self.assertTrue(lines[2].startswith(b":3: EncodingWarning: ")) + def test_shell(self): + # Test basic echo command + proc = subprocess.shell("echo Python", stdout=subprocess.PIPE, text=True) + self.assertEqual(proc.returncode, 0) + self.assertEqual(proc.stdout.rstrip(), "Python") + + # cmd type must be str + with self.assertRaises(TypeError): + subprocess.shell(b"echo Python") + with self.assertRaises(TypeError): + subprocess.shell(["echo", "Python"]) + + # Passing shell=False is invalid + with self.assertRaises(ValueError): + subprocess.shell("echo Python", shell=False) + def _get_test_grp_name(): for name_group in ('staff', 'nogroup', 'grp', 'nobody', 'nfsnobody'): diff --git a/Misc/NEWS.d/next/Library/2024-06-27-14-30-58.gh-issue-120952.nu6Ejc.rst b/Misc/NEWS.d/next/Library/2024-06-27-14-30-58.gh-issue-120952.nu6Ejc.rst new file mode 100644 index 00000000000000..8eebf759ede563 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-27-14-30-58.gh-issue-120952.nu6Ejc.rst @@ -0,0 +1,3 @@ +Add :func:`subprocess.shell` function to run a shell command. Read the +:ref:`Security Considerations ` section before using +this function. Patch by Victor Stinner.