Search code examples
pythonsshquotes

How to transport " for remote execution?


I am working on some python tooling to configure remote systems. One configuration step requires to replace values in a text file. I thought to do a simple

ssh [email protected] sed -i -e s/property=\"true\"/property=\"false\"/g the.remote.file

But well, it doesnt work.

I tried shlex + subprocess, I tried pxssh (which I would like to avoid anyway, as it is much slower compared to a ssh call via subprocess). It seems that whatever I try, the quotes around true/false are removed at some level. But the quotes are in that file on the remote system; so sed will do nothing if the quotes are not showing up in the call that is executed on the remote system.

I could get it working when invoking ssh directly from my bash; but from within python ... no chance. In the end, I put that string into a file; use scp to copy that file to the remote system; and then I ran it from there using ssh again.

But still wondering - is there a clean, straight forward way to do this "just using python"?

(Note: I am looking for solutions that work out of box; not something that requires my users to install other "third party" modules like paramiko.)

Update:

This is ... crazy. I just ran this code (which I think looks exactly like the code that is given in the answer --- I added the .format() afterwards to easily hide username/ip from the posted text):

cmd_str= 'ssh {} "cat ~/prop"'.format(target)
subprocess.call(cmd_str, shell=True)

cmd_str= 'ssh {} "sed -i -e s/property=\"true\"/property=\"false\"/g ~/prop"'.format(target)
subprocess.call(cmd_str, shell=True)

cmd_str= 'ssh {} "cat ~/prop"'.format(target)
subprocess.call(cmd_str, shell=True)`

and I get:

property="true"
property="true"

Could that depend on the configuration of the sshd on the remote system?


Solution

  • Everything worked fine for me, I tried this:

    In [1]: import subprocess
    In [2]: cmd = 'ssh [email protected] "sed -i -e s/property=\"true\"/property=\"false\"/g ~/cfg.cfg"'
    In [3]: subprocess.call(cmd, shell=True)
    Out[3]: 0
    

    and as shell=True can be unsafe I also tried this:

    In [4]: parts = ['ssh', '[email protected]', '`sed -i -e s/property=\"true\"/property=\"false\"/g ~/cfg.cfg`']
    In [5]: subprocess.call(parts)
    Out[5]: 0
    

    In both cases my file was changed.

    UPDATE: shlex output

    In [27]: shlex.split(cmd)
    Out[27]: 
    ['ssh',
     '[email protected]',
     'sed -i -e s/property=true/property=false/g ~/cfg.cfg']
    

    Also note, In the first case I use double quotes for the remote command but in the second case I use back quotes for the command.

    UPDATE 2:

    In [9]: subprocess.call(['ssh', '[email protected]', 'cat ~/cfg.cfg'])
    property="true"
    Out[9]: 0
    In [10]: subprocess.call(['ssh', '[email protected]', '`sed -i -e s/property=\\"true\\"/property=\\"false\\"/g ~/cfg.cfg`'])
    Out[10]: 0
    In [11]: subprocess.call(['ssh', '[email protected]', 'cat ~/cfg.cfg'])
    property="false"
    Out[11]: 0
    

    UPDATE 3:

    In [22]: cmd = 'ssh [email protected] \'`sed -i -e s/property=\\"true\\"/property=\\"false\\"/g ~/cfg.cfg`\''
    In [23]: subprocess.call(['ssh', '[email protected]', 'cat ~/cfg.cfg'])
    property="true"
    Out[23]: 0
    In [24]: subprocess.call(cmd, shell=True)
    Out[24]: 0
    In [25]: subprocess.call(['ssh', '[email protected]', 'cat ~/cfg.cfg'])
    property="false"
    Out[25]: 0
    In [26]: shlex.split(cmd)
    Out[26]: 
    ['ssh',
     '[email protected]',
     '`sed -i -e s/property=\\"true\\"/property=\\"false\\"/g ~/cfg.cfg`']