]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-151485: Fix command quoting in subprocess.CalledProcessError.__str__ (#151486)
authorBenjy Wiener <info@benjywiener.com>
Wed, 24 Jun 2026 14:11:02 +0000 (17:11 +0300)
committerGitHub <noreply@github.com>
Wed, 24 Jun 2026 14:11:02 +0000 (16:11 +0200)
CalledProcessError previously formatted cmd as `"... '%s' ..."`. This lead to
unbalanced quoting when cmd contains single-quotes or, more commonly, when cmd
is a list. This change updates the relevant format strings to use %r instead.

Co-authored-by: Benjy Wiener <benjywiener@gmail.com>
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
Lib/subprocess.py
Lib/test/test_subprocess.py
Misc/NEWS.d/next/Library/2026-06-15-07-42-12.gh-issue-151485.NiYQPZ.rst [new file with mode: 0644]

index 38b655f2f7b9d2bce75af6225a5cf27408de6967..6fe2ec98fb4088872849fac97c8fb6832a732333 100644 (file)
@@ -145,13 +145,13 @@ class CalledProcessError(SubprocessError):
     def __str__(self):
         if self.returncode and self.returncode < 0:
             try:
-                return "Command '%s' died with %r." % (
+                return "Command %r died with %r." % (
                         self.cmd, signal.Signals(-self.returncode))
             except ValueError:
-                return "Command '%s' died with unknown signal %d." % (
+                return "Command %r died with unknown signal %d." % (
                         self.cmd, -self.returncode)
         else:
-            return "Command '%s' returned non-zero exit status %d." % (
+            return "Command %r returned non-zero exit status %d." % (
                     self.cmd, self.returncode)
 
     @property
index d41cb1294a3dafe7eea24800d7ec7f545aa3d418..d066ae85dfc51a649ef1be224f99de90cfba0a3c 100644 (file)
@@ -2428,26 +2428,26 @@ class POSIXProcessTestCase(BaseTestCase):
             p.wait()
         self.assertEqual(-p.returncode, signal.SIGABRT)
 
-    def test_CalledProcessError_str_signal(self):
+    def test_CalledProcessError_str(self):
+        # command string
+        err = subprocess.CalledProcessError(2, "fake cmd")
+        self.assertEqual(str(err), "Command 'fake cmd' returned non-zero exit status 2.")
+
+        # command string with a single-quote
+        err = subprocess.CalledProcessError(2, "fake ' cmd")
+        self.assertEqual(str(err), 'Command "fake \' cmd" returned non-zero exit status 2.')
+
+        # command list
+        err = subprocess.CalledProcessError(2, ["fake", "cmd"])
+        self.assertEqual(str(err), "Command ['fake', 'cmd'] returned non-zero exit status 2.")
+
+        # signal
         err = subprocess.CalledProcessError(-int(signal.SIGABRT), "fake cmd")
-        error_string = str(err)
-        # We're relying on the repr() of the signal.Signals intenum to provide
-        # the word signal, the signal name and the numeric value.
-        self.assertIn("signal", error_string.lower())
-        # We're not being specific about the signal name as some signals have
-        # multiple names and which name is revealed can vary.
-        self.assertIn("SIG", error_string)
-        self.assertIn(str(signal.SIGABRT), error_string)
-
-    def test_CalledProcessError_str_unknown_signal(self):
-        err = subprocess.CalledProcessError(-9876543, "fake cmd")
-        error_string = str(err)
-        self.assertIn("unknown signal 9876543.", error_string)
+        self.assertEqual(str(err), f"Command 'fake cmd' died with {signal.SIGABRT!r}.")
 
-    def test_CalledProcessError_str_non_zero(self):
-        err = subprocess.CalledProcessError(2, "fake cmd")
-        error_string = str(err)
-        self.assertIn("non-zero exit status 2.", error_string)
+        # unknown signal
+        err = subprocess.CalledProcessError(-9876543, "fake cmd")
+        self.assertEqual(str(err), "Command 'fake cmd' died with unknown signal 9876543.")
 
     def test_preexec(self):
         # DISCLAIMER: Setting environment variables is *not* a good use
diff --git a/Misc/NEWS.d/next/Library/2026-06-15-07-42-12.gh-issue-151485.NiYQPZ.rst b/Misc/NEWS.d/next/Library/2026-06-15-07-42-12.gh-issue-151485.NiYQPZ.rst
new file mode 100644 (file)
index 0000000..1344991
--- /dev/null
@@ -0,0 +1 @@
+Fix command quoting in :exc:`subprocess.CalledProcessError`. Contributed by Benjy Wiener.