python

Quote shell characters

Sometimes it’s nice to be able to display a command being run and do it in a form that can be repeated from a terminal window. Unfortunately, I’ll build a command in python like this:

args = ['somecmd', 'WHERE=$(DESTDIR)', '--message', "Lot's of text" ]

The trivial answer of ‘ ‘.join (args) produces something that can’t be passed to the shell:

>>> ' '.join (args)
"somecmd WHERE=$(DESTDIR) --message Lot's of text"

Here is a code fragment and some attached tests that provide better quoting. It’s used like this:

>>> import shquote
>>> args = ['somecmd', 'WHERE=$(DESTDIR)', '--message', "Lot's of text" ]
>>> print ' '.join ([shquote.shquote (a) for a in args])
somecmd 'WHERE=$(DESTDIR)' --message "Lot's of text"
#!/usr/bin/env python
def shquote (arg):
    """
    Quote a single argument in the most readable way so it is safe from
    shell expansion.
    """
    # Return an empty string as double quoted
    if not arg:
        return '""'
    special = """ \t[]{}()'*?|;"""   # These need double quotes
    superspecial = """$"!"""        # These need single quotes
    quotechar = None
    # See if we need single quotes
    for c in superspecial:
        if c in arg:
            quotechar = "'"
            break
    # See if we need double quotes
    if not quotechar:
        for c in special:
            if c in arg:
                quotechar = '"'
                break
    # No quoting necessary
    if not quotechar:
        return arg
    # If quotechar is present then escape it by dropping out of quotes
    if quotechar in arg:
        arg = arg.replace (quotechar, "%s\\%s%s" % (quotechar, quotechar, quotechar))
    return quotechar + arg + quotechar
if __name__ == '__main__':
    tests = [('',       '""'),
             ('*.cpp',  '"*.cpp"'),
             ('test.[ch]', '"test.[ch]"'),
             ('(',      '"("'),
             (')',      '")"'),
             ('a',      'a'),
             ('$',      """'$'"""),
             ('$a',     """'$a'"""),
             ('abc|def', '"abc|def"'),
             ('abc;def', '"abc;def"'),
             ('a b',    '"a b"'),
             ('"abc"',  """'"abc"'"""),
             ("""It's mine""", '"It\'s mine"'),
             ('abc | def ABC=$(XYC)', """'abc | def ABC=$(XYC)'"""),
             (""""That's impossible!" he said.""", """'"That'\\''s impossible!" he said.'"""),
          ]
    for (input, expected) in tests:
        output = shquote (input)
        if output != expected:
            print 'input    = ', input
            print 'expected = ', expected
            print 'got      = ', output
        else:
            print "%-30s => %s" % (input, output)

And the result of running this:

bash-3.2$ ./shquote.py
                               => ""
*.cpp                          => "*.cpp"
test.[ch]                      => "test.[ch]"
(                              => "("
)                              => ")"
a                              => a
$                              => '$'
$a                             => '$a'
abc|def                        => "abc|def"
abc;def                        => "abc;def"
a b                            => "a b"
"abc"                          => '"abc"'
It's mine                      => "It's mine"
abc | def ABC=$(XYC)           => 'abc | def ABC=$(XYC)'
"That's impossible!" he said.  => '"That'\''s impossible!" he said.'

Trivial subset sum problem

I was asked in a telephone interview how to find out if there are two numbers in
a list that sum to a certain value. I did an O(N^2) algorithm, then refined it to an O(n lg n) algorithm. Unfortunately, there’s an O(n) algorithm which I didn’t think of on the phone but came up with after the fact:

def somePair(values, Z):
    """
    Given a list of numbers and value Z, are there any pair of numbers such that X+Y = Z?
    This does this in O(N) time looking at each value, val, and checking if Z-val is
    already in a lookup table.
    """
    lookup = set()         # Store the values
    for val in values:
        diff = Z - val
        if diff in lookup:
            return True
        else:
            lookup.add(val)
    return False
if __name__ == '__main__':
    assert somePair([], 4) == False
    assert somePair([4], 4) == False
    assert somePair([1,2,3,4], 1) == False
    assert somePair([1,2,3,4], 2) == False
    assert somePair([-8, 12], 4)
    assert somePair([1,2,3,4], 3)
    assert somePair([1,2,3,4, 4], 3)
    assert somePair([1,2,3,4], 4)