pythonstringpython-3.6shlex

shlex.split() returning whole command as a single string


shlex.split() is not giving the proper output on the input string.

In python interpreter, storing the input value in a variable produces the expected output.

But if i execute via a script, shlex.split() output is incorrect and input string did not split on whitespace.

>>> import shlex

>>> var = "/usr/bin/ansible-playbook --timeout=60 --module-path /var/sandeep> /playbooks/ --extra-vars '{ \"text\": \"DUMMY\", \"addition\": [\"1\", \"2\", \"3\", ], \"deletion\": [], \"update\": \"update\", \"path\": \"/var/sandeep\", }' /tmp/sandeep//tmp/example.yaml"
>>>
>>>
>>> shlex.split(var)

['/usr/bin/ansible-playbook', '--timeout=60', '--module-path', '/var/sandeep/playbooks/', '--extra-vars', '{ "text": "DUMMY", "addition": ["1", "2", "3", ], "deletion": [], "update": "update", "path": "/var/sandeep", }', '/tmp/sandeep//tmp/example.yaml']
def create_extra(text, extra_dict):
    extra = "'{{ \\\"text\\\": \\\"{}\\\", ".format(text)
    for key, value in extra_dict.items():
        if isinstance(value, list):
            extra += '\\\"{}\\\": ['.format(key)
            for item in value:
                extra += '\\\"{}\\\", '.format(item)
            extra += '], '
        elif isinstance(value, dict):
            extra += '\\\"{}\\\": {{'.format(key)
            for item_key, item_value in value.items():
                extra += '\\\"{}\\\": \\\"{}\\\", '.format(item_key, item_value)
            extra += "}, "
        else:
            extra += '\\\"{}\\\": \\\"{}\\\", '.format(key, value)
    extra += "}'"
    #print("extra: %s" % extra)
    return extra

extra_dict = {'addition': ["1", "2", "3"],
                   'deletion': [],
                   'update': 'update',
                   'path' : '/var/sandeep'
                  }


temp = create_extra("DUMMY", extra_dict)

"""create_extra function formats and return string"""

cmd = ('"/usr/bin/ansible-playbook ' +
        '--timeout=60 '  +
        '--module-path /var/sandeep/playbooks/ ' +
        '--extra-vars {} {}/{}"'.format(temp, "/tmp/sandeep", "/tmp/example.yaml"))

print(cmd)
print(shlex.split(cmd))
output of print(cmd)
"/usr/bin/ansible-playbook --timeout=60 --module-path /var/sandeep/playbooks/ --extra-vars '{ \"text\": \"DUMMY\", \"addition\": [\"1\", \"2\", \"3\", ], \"deletion\": [], \"update\": \"update\", \"path\": \"/var/sandeep\", }' /tmp/sandeep//tmp/example.yaml"


Expected results:
['/usr/bin/ansible-playbook', '--timeout=60', '--module-path', '/var/sandeep/playbooks/', '--extra-vars', '{ "text": "DUMMY", "addition": ["1", "2", "3", ], "deletion": [], "update": "update", "path": "/var/sandeep", }', '/tmp/sandeep//tmp/example.yaml']


Actual Results:
['/usr/bin/ansible-playbook --timeout=60 --module-path /var/sandeep/playbooks/ --extra-vars \'{ "text": "DUMMY", "addition": ["1", "2", "3", ], "deletion": [], "update": "update", "path": "/var/sandeep", }\' /tmp/sandeep//tmp/example.yaml']

Am i missing something here?


Solution

  • The shlex output is completely correct, because of the literal " characters contained in your string.

    cmd = ('"/usr/bin/ansible-playbook ' +
    #       ^- that right there
            '--timeout=60 '  +
            '--module-path /var/sandeep/playbooks/ ' +
            '--extra-vars {} {}/{}"'.format(temp, "/tmp/sandeep", "/tmp/example.yaml"))
    #        and this right here -^
    

    As your print(cmd) thus shows:

    "/usr/bin/ansible-playbook --timeout=60 --module-path /var/sandeep/playbooks/ --extra-vars whatever /tmp/sandeep//tmp/example.yaml"
    

    ...your string starts with a " and ends with a ", and that makes it a single, literal string when parsed by a shell.


    Just take those characters out, and the issue no longer happens:

    cmd = ('/usr/bin/ansible-playbook ' +
           '--timeout=60 '  +
           '--module-path /var/sandeep/playbooks/ ' +
           '--extra-vars {} {}/{}'.format(temp, "/tmp/sandeep", "/tmp/example.yaml"))
    
    print(cmd)
    print(shlex.split(cmd))
    

    However, you have other serious bugs, because string concatenation is inherently unsuited to building command lines. Instead of trying to take that approach at all, just build an array directly:

    cmd = ['/usr/bin/ansible-playbook',
           '--timeout=60',
           '--module-path', '/var/sandeep/playbooks/',
           '--extra-vars', temp, os.path.join('/tmp/sandeep', '/tmp/example.yml')]
    

    ...and then values of temp or other variables with spaces or literal quotes will no longer break your code or allow arbitrary arguments to be injected.