37

Is there anything in the Python standard library that will properly parse/unparse strings for using in shell commands? I'm looking for the python analog to perl's String::ShellQuote::shell_quote:

$ print String::ShellQuote::shell_quote("hello", "stack", "overflow's", "quite", "cool")
hello stack 'overflow'\''s' quite cool

And, even more importantly, something which will work in the reverse direction (take a string and decompose it into a list).

Zero Piraeus
  • 56,143
  • 27
  • 150
  • 160
YGA
  • 9,546
  • 15
  • 47
  • 50

8 Answers8

33

Looks like

try:  # py3
    from shlex import quote
except ImportError:  # py2
    from pipes import quote

quote("hello stack overflow's quite cool")
>>> '"hello stack overflow\'s quite cool"'

gets me far enough.

Stephen Fuhry
  • 12,624
  • 6
  • 56
  • 55
YGA
  • 9,546
  • 15
  • 47
  • 50
10

pipes.quote is now shlex.quote in python 3. It is easy enough to use that piece of code.

https://github.com/python/cpython/blob/master/Lib/shlex.py#L281

That version handles zero-length argument correctly.

dnozay
  • 23,846
  • 6
  • 82
  • 104
8

To unquote, try shlex.split()

DomQ
  • 4,184
  • 38
  • 37
7

I'm pretty sure that pipes.quote is broken, and should not be used, because it does not handle zero-length arguments correctly:

>>> from pipes import quote
>>> args = ['arg1', '', 'arg3']
>>> print 'mycommand %s' % (' '.join(quote(arg) for arg in args))
mycommand arg1  arg3

I believe the result should be something like

mycommand arg1 '' arg3
John Wiseman
  • 3,081
  • 1
  • 22
  • 31
6

For shell quoting, this works: I've rigorously tested it on Posix. [I'm assuming that the list2cmdline function supplied by Python works as advertised on Windows]

# shell.py
import os
if os.name == 'nt':
    from subprocess import list2cmdline

    def quote(arg):
        return list2cmdline([arg])[0]
else:
    import re
    _quote_pos = re.compile('(?=[^-0-9a-zA-Z_./\n])')

    def quote(arg):
        r"""
        >>> quote('\t')
        '\\\t'
        >>> quote('foo bar')
        'foo\\ bar'
        """
        # This is the logic emacs uses
        if arg:
            return _quote_pos.sub('\\\\', arg).replace('\n',"'\n'")
        else:
            return "''"

    def list2cmdline(args):
        return ' '.join([ quote(a) for a in args ])

The tests are here, if anyone cares.

Dave Abrahams
  • 61
  • 1
  • 1
  • Counter example: string to be quoted contains `"\xC3\xA9"`, which is an é in UTF-8, and thus not uncommon in filenames. Code above puts backslashes in front of both characters, which is incorrect. `pipes.quote` will put it in single quotes. – greggo Jan 26 '15 at 03:16
2

The quotefunction is available for quite some time (Python 2.7?) -- the major drawback is it moved from pipe module to shlex between 3.2 and 3.3.

You have to be prepared to handle both cases while importing that function:

try:
    from shlex import quote
except ImportError:
    from pipes import quote
Sylvain Leroux
  • 50,096
  • 7
  • 103
  • 125
2

The standard library module subprocess has the list2cmdline function which does this, albeit according to Microsoft rules so I am not sure how reliable it works in Unix-like environments for more complicated command lines.

sjngm
  • 12,423
  • 14
  • 84
  • 114
Alex
  • 21
  • 1
0

You should never have to shell quote. The correct way to do a command is to not do shell quoting and instead use subprocess.call or subprocess.Popen, and pass a list of unquoted arguments. This is immune to shell expansion.

i.e.

subprocess.Popen(['echo', '"', '$foo'], shell=False)

If you want to unquote shell quoted data, you can use shlex.shlex like this:

list(shlex.shlex("hello stack 'overflow'\''s' quite cool"))
Stephan202
  • 59,965
  • 13
  • 127
  • 133
Jerub
  • 41,746
  • 15
  • 73
  • 90
  • 12
    What if I need to pass a command (that requires escaping) for ssh to execute once it reaches the other side? – Mike Boers Jun 09 '09 at 04:00
  • 13
    This is not a helpful answer (well it answers one half my question, so it's half helpful...). There are are any number of occasions when you need to shell quote -- Mike Boers gives just one great example (in fact, that's the one I'm running into) – YGA Jun 09 '09 at 16:54
  • actually even worse, the given example breaks: (Pdb) list(shlex.shlex("hello stack 'overflow'\''s' quite cool")) *** Error in argument: '(shlex.shlex("hello stack \'overflow\'\\\'\'s\' quite cool"))' – YGA Jun 09 '09 at 17:09
  • 1
    From subprocess documentation: If shell is True, it is recommended to pass args as a string rather than as a sequence. ... This includes, for example, quoting or backslash escaping filenames with spaces in them. - That's just another example where you need quoting. – Seppo Enarvi Oct 29 '12 at 13:43
  • This answer is not good, you may be generating a shell script to run later and not executing the command directly. – ideasman42 Oct 30 '12 at 03:22
  • 5
    My use case for shell quoting is: my Python program is using subprocess.Popen() as suggested, but when it does I'd like it to print something to the console which can be directly cut-and-pasted into a shell, for manual debugging should the command fail. – user23614 Jul 25 '14 at 09:06