]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Add shlex.quote function, to escape filenames and command lines (#9723).
authorÉric Araujo <merwok@netwok.org>
Wed, 27 Jul 2011 16:29:31 +0000 (18:29 +0200)
committerÉric Araujo <merwok@netwok.org>
Wed, 27 Jul 2011 16:29:31 +0000 (18:29 +0200)
This function used to live as pipes.quote, where it was undocumented but
used anyway.  (An alias still exists for backward compatibility.)  The
tests have been moved as is, but the code of the function was changed to
use a regex instead of a loop with string comparisons (at Ian Bicking’s
suggestion).  I’m terrible at regexes, so any feedback is welcome.

Doc/library/shlex.rst
Doc/library/subprocess.rst
Lib/pipes.py
Lib/shlex.py
Lib/test/test_pipes.py
Lib/test/test_shlex.py
Misc/NEWS

index 0113fb7db37378fcb0a179c484b8976291a9f489..e5aec4a936f6890ca2062efaa0ba3b4d4098d510 100644 (file)
@@ -34,6 +34,22 @@ The :mod:`shlex` module defines the following functions:
       passing ``None`` for *s* will read the string to split from standard
       input.
 
+
+.. function:: quote(s)
+
+   Return a shell-escaped version of the string *s*.  The returned value is a
+   string that can safely be used as one token in a shell command line.
+   Examples::
+
+      >>> filename = 'somefile; rm -rf /home'
+      >>> command = 'ls -l {}'.format(quote(filename))
+      >>> print(command)
+      ls -l 'somefile; rm -rf /home'
+      >>> remote_command = 'ssh home {}'.format(quote(command))
+      >>> print(remote_command)
+      ssh home 'ls -l '"'"'somefile; rm -rf /home'"'"''
+
+
 The :mod:`shlex` module defines the following class:
 
 
@@ -282,5 +298,4 @@ parsing rules.
 
 * EOF is signaled with a :const:`None` value;
 
-* Quoted empty strings (``''``) are allowed;
-
+* Quoted empty strings (``''``) are allowed.
index 7e759f0d03c9b11eb76b62f217bf17273bd1eb39..2c7613027bd0bc45b952b5b63129fe446efb4a18 100644 (file)
@@ -92,7 +92,8 @@ This module defines one class called :class:`Popen`:
          >>> call("cat " + filename, shell=True) # Uh-oh. This will end badly...
 
       *shell=False* does not suffer from this vulnerability; the above Note may be
-      helpful in getting code using *shell=False* to work.
+      helpful in getting code using *shell=False* to work.  See also
+      :func:`shlex.quote` for a function useful to quote filenames and commands.
 
    On Windows: the :class:`Popen` class uses CreateProcess() to execute the
    child program, which operates on strings.  If *args* is a sequence, it will
@@ -871,3 +872,7 @@ runtime):
    described in rule 3.
 
 
+.. seealso::
+
+   :mod:`shlex`
+      Module which provides function to parse and escape command lines.
index 51666a8ae773e7dccb718d552027c51c2666dae8..693309fff0103029ba05a45457f944b6cf39420b 100644 (file)
@@ -62,7 +62,9 @@ For an example, see the function test() at the end of the file.
 import re
 import os
 import tempfile
-import string
+# we import the quote function rather than the module for backward compat
+# (quote used to be an undocumented but used function in pipes)
+from shlex import quote
 
 __all__ = ["Template"]
 
@@ -245,22 +247,3 @@ def makepipeline(infile, steps, outfile):
         cmdlist = trapcmd + '\n' + cmdlist + '\n' + rmcmd
     #
     return cmdlist
-
-
-# Reliably quote a string as a single argument for /bin/sh
-
-# Safe unquoted
-_safechars = frozenset(string.ascii_letters + string.digits + '@%_-+=:,./')
-
-def quote(file):
-    """Return a shell-escaped version of the file string."""
-    for c in file:
-        if c not in _safechars:
-            break
-    else:
-        if not file:
-            return "''"
-        return file
-    # use single quotes, and put single quotes into double quotes
-    # the string $'b is then quoted as '$'"'"'b'
-    return "'" + file.replace("'", "'\"'\"'") + "'"
index 3edd3db1ed9ed445cba637570e6a04a2652765b3..279ab484059514c145917d746f3cb3d52a767599 100644 (file)
@@ -6,13 +6,14 @@
 # Posix compliance, split(), string arguments, and
 # iterator interface by Gustavo Niemeyer, April 2003.
 
-import os.path
+import os
+import re
 import sys
 from collections import deque
 
 from io import StringIO
 
-__all__ = ["shlex", "split"]
+__all__ = ["shlex", "split", "quote"]
 
 class shlex:
     "A lexical analyzer class for simple shell-like syntaxes."
@@ -274,6 +275,21 @@ def split(s, comments=False, posix=True):
         lex.commenters = ''
     return list(lex)
 
+
+_find_unsafe = re.compile(r'[^\w\d@%_\-\+=:,\./]').search
+
+def quote(s):
+    """Return a shell-escaped version of the string *s*."""
+    if not s:
+        return "''"
+    if _find_unsafe(s) is None:
+        return s
+
+    # use single quotes, and put single quotes into double quotes
+    # the string $'b is then quoted as '$'"'"'b'
+    return "'" + s.replace("'", "'\"'\"'") + "'"
+
+
 if __name__ == '__main__':
     if len(sys.argv) == 1:
         lexer = shlex()
index f2b58d5e3d6484c88daee4b223e1912b3693a770..6a7b45fb4666e75ff8f4c5c3122e455756cbb530 100644 (file)
@@ -79,20 +79,6 @@ class SimplePipeTests(unittest.TestCase):
         with open(TESTFN) as f:
             self.assertEqual(f.read(), d)
 
-    def testQuoting(self):
-        safeunquoted = string.ascii_letters + string.digits + '@%_-+=:,./'
-        unsafe = '"`$\\!'
-
-        self.assertEqual(pipes.quote(''), "''")
-        self.assertEqual(pipes.quote(safeunquoted), safeunquoted)
-        self.assertEqual(pipes.quote('test file name'), "'test file name'")
-        for u in unsafe:
-            self.assertEqual(pipes.quote('test%sname' % u),
-                              "'test%sname'" % u)
-        for u in unsafe:
-            self.assertEqual(pipes.quote("test%s'name'" % u),
-                             "'test%s'\"'\"'name'\"'\"''" % u)
-
     def testRepr(self):
         t = pipes.Template()
         self.assertEqual(repr(t), "<Template instance, steps=[]>")
index 25e4b6df6c5d2403c5d2f7b3431ffa03da663541..ea3d777ceba37ff7b7c36b8744df4fc851c96272 100644 (file)
@@ -1,6 +1,7 @@
-import unittest
-import os, sys, io
+import io
 import shlex
+import string
+import unittest
 
 from test import support
 
@@ -173,6 +174,21 @@ class ShlexTest(unittest.TestCase):
                              "%s: %s != %s" %
                              (self.data[i][0], l, self.data[i][1:]))
 
+    def testQuote(self):
+        safeunquoted = string.ascii_letters + string.digits + '@%_-+=:,./'
+        unsafe = '"`$\\!'
+
+        self.assertEqual(shlex.quote(''), "''")
+        self.assertEqual(shlex.quote(safeunquoted), safeunquoted)
+        self.assertEqual(shlex.quote('test file name'), "'test file name'")
+        for u in unsafe:
+            self.assertEqual(shlex.quote('test%sname' % u),
+                             "'test%sname'" % u)
+        for u in unsafe:
+            self.assertEqual(shlex.quote("test%s'name'" % u),
+                             "'test%s'\"'\"'name'\"'\"''" % u)
+
+
 # Allow this test to be used with old shlex.py
 if not getattr(shlex, "split", None):
     for methname in dir(ShlexTest):
index 857accee27a3a9acfef7d9debde82bacc1d2a2d3..3cd8018a26a6c32903ce3bfb5f214e8fe9ef1615 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -237,6 +237,9 @@ Core and Builtins
 Library
 -------
 
+- Issue #9723: Add shlex.quote functions, to escape filenames and command
+  lines.
+
 - Issue #12607: In subprocess, fix issue where if stdin, stdout or stderr is
   given as a low fd, it gets overwritten.
 
@@ -6674,4 +6677,4 @@ Docs
 ----
 
 
-**(For information about older versions, consult the HISTORY file.)**
\ No newline at end of file
+**(For information about older versions, consult the HISTORY file.)**