Search code examples
python-3.xsubprocessquoting

Python3 run a complex shell command with multiple intended quotations


When i run from bash:

# su -c "psql -d mapping -c \"INSERT INTO mapping (ip,username) VALUES('1.2.3.4','test');\"" postgres

It works fine, but when i try from python:

subprocess.run("su -c \"psql -d mapping -c \"INSERT INTO mapping (ip,username) VALUES('1.2.3.4','test');\"\" postgres")

It fails, i have tried different quotation marks and all failing. Could you please help?


Solution

  • There are two solutions, depending on whether or not you use the shell from Python. The trivial but inefficient solution is to pass the string with shell=True and basically just add Python quoting around it.

    subprocess.run(r'''su -c "psql -d mapping -c \"INSERT INTO mapping (ip,username) VALUES('1.2.3.4','test');\"" postgres''', shell=True,
        # For good measure, you should check its status
        check=True)
    

    More efficiently and perhaps in fact more readably, you can remove the shell from the equation, and split the command into strings yourself.

    subprocess.run([
            'su', '-c',
            # The argument to "su -c" should be one string
            # Because we escaped the shell, the double quotes don't need escaping
            '''psql -d mapping -c "INSERT INTO mapping (ip,username) VALUES('1.2.3.4','test');"''',
            'postgres'],
        check=True)
    

    Notice how with shell=True the first argument is a string which gets passed to the shell, whereas without it, you pass a list of tokens directly to the OS-level exec() or (somewhat less straightforwardly on Windows) CreateProcess(). Notice also how in the first instance I used an r'...' string to avoid having Python meddle with the backslashes in the string.