]> granicus.if.org Git - python/commitdiff
bpo-22454: Add shlex.join() (the opposite of shlex.split()) (GH-7605)
authorBo Bayles <bbayles@gmail.com>
Wed, 29 May 2019 08:06:12 +0000 (03:06 -0500)
committerVinay Sajip <vinay_sajip@yahoo.co.uk>
Wed, 29 May 2019 08:06:11 +0000 (09:06 +0100)
Doc/library/shlex.rst
Doc/whatsnew/3.8.rst
Lib/shlex.py
Lib/test/test_shlex.py
Misc/NEWS.d/next/Library/2018-06-10-17-48-07.bpo-22454.qeiy_X.rst [new file with mode: 0644]

index fb335c690068165e1d34b69e0780b193c4c7ecbb..8c5b0239d1f08b8fa7f7c85fca0b081b3b097f93 100644 (file)
@@ -37,6 +37,21 @@ The :mod:`shlex` module defines the following functions:
       standard input.
 
 
+.. function:: join(split_command)
+
+   Concatenate the tokens of the list *split_command* and return a string.
+   This function is the inverse of :func:`split`.
+
+      >>> from shlex import join
+      >>> print(join(['echo', '-n', 'Multiple words']))
+      echo -n 'Multiple words'
+
+   The returned value is shell-escaped to protect against injection
+   vulnerabilities (see :func:`quote`).
+
+   .. versionadded:: 3.8
+
+
 .. function:: quote(s)
 
    Return a shell-escaped version of the string *s*.  The returned value is a
index d1305dc1e7dfb74098e0b223d85779e172e791ca..f704b47098e64778286d14f2ef6cb07a5f0c558b 100644 (file)
@@ -552,6 +552,11 @@ convenience functions to automate the necessary tasks usually involved when
 creating a server socket, including accepting both IPv4 and IPv6 connections
 on the same socket.  (Contributed by Giampaolo Rodola in :issue:`17561`.)
 
+shlex
+----------
+
+The new :func:`shlex.join` function acts as the inverse of :func:`shlex.split`.
+(Contributed by Bo Bayles in :issue:`32102`.)
 
 shutil
 ------
index 2c9786c517a350f2bd766b539e41ac860f9dd435..fb1130d4eac207f8b6ddd596fa2b8859a9e741b6 100644 (file)
@@ -14,7 +14,7 @@ from collections import deque
 
 from io import StringIO
 
-__all__ = ["shlex", "split", "quote"]
+__all__ = ["shlex", "split", "quote", "join"]
 
 class shlex:
     "A lexical analyzer class for simple shell-like syntaxes."
@@ -305,6 +305,11 @@ def split(s, comments=False, posix=True):
     return list(lex)
 
 
+def join(split_command):
+    """Return a shell-escaped string from *split_command*."""
+    return ' '.join(quote(arg) for arg in split_command)
+
+
 _find_unsafe = re.compile(r'[^\w@%+=:,./-]', re.ASCII).search
 
 def quote(s):
index fd35788e81b27203b9a5825f498fff74cf7c79aa..a432610d3af4605b49b85d92494e23cc8dc004f3 100644 (file)
@@ -308,6 +308,26 @@ class ShlexTest(unittest.TestCase):
             self.assertEqual(shlex.quote("test%s'name'" % u),
                              "'test%s'\"'\"'name'\"'\"''" % u)
 
+    def testJoin(self):
+        for split_command, command in [
+            (['a ', 'b'], "'a ' b"),
+            (['a', ' b'], "a ' b'"),
+            (['a', ' ', 'b'], "a ' ' b"),
+            (['"a', 'b"'], '\'"a\' \'b"\''),
+        ]:
+            with self.subTest(command=command):
+                joined = shlex.join(split_command)
+                self.assertEqual(joined, command)
+
+    def testJoinRoundtrip(self):
+        all_data = self.data + self.posix_data
+        for command, *split_command in all_data:
+            with self.subTest(command=command):
+                joined = shlex.join(split_command)
+                resplit = shlex.split(joined)
+                self.assertEqual(split_command, resplit)
+
+
 # Allow this test to be used with old shlex.py
 if not getattr(shlex, "split", None):
     for methname in dir(ShlexTest):
diff --git a/Misc/NEWS.d/next/Library/2018-06-10-17-48-07.bpo-22454.qeiy_X.rst b/Misc/NEWS.d/next/Library/2018-06-10-17-48-07.bpo-22454.qeiy_X.rst
new file mode 100644 (file)
index 0000000..2f30e5c
--- /dev/null
@@ -0,0 +1,2 @@
+The :mod:`shlex` module now exposes :func:`shlex.join`, the inverse of
+:func:`shlex.split`. Patch by Bo Bayles.