0

I have a python program which needs to call a script on a remote system via ssh.

This ssh call needs to happen (once) at a specified date which can be done via the linux at command.

I am able to call both of these external bash commands using either the os module or the subprocess module from my python program. The issue comes when passing certain arguments to the remote script.

In addition to being run remotely and at a later date, the (bash) script I wish to call requires several arguments to be passed to it, these arguments are python variables which I wish to pass on to the script.

user="user@remote"
arg1="argument with spaces"
arg2="two"
cmd="ssh "+user+"' /home/user/path/script.sh "+arg1+" "+arg2+"'" 
os.system(cmd)

One of these arguments is a string which contains spaces but would ideally be passed as a single argument;

for example:

./script.sh "Argument with Spaces" where $1 is equal to "Argument with Spaces"

I have tried various combinations of escaping double and single quotes in both python and the string itself and the use of grave accents around the entire ssh command. The most successful version calls the script with the arguments as desired, but ignores the at command and runs immediately.

Is there a clean way within python to accomplish this?

hlovdal
  • 23,353
  • 10
  • 78
  • 148
John
  • 1,486
  • 4
  • 24
  • 39

1 Answers1

2

new answer

now that you edited your question you should probably be using format strings

cmd = '''ssh {user} "{cmd} '{arg0}' '{arg1}'"'''.format(user="user@remote",cmd="somescript",arg0="hello",arg2="hello world")
print cmd

old answer

I think you can use a -c switch with ssh to execute some code on a remote machine (ssh user@host.net -c "python myscript.py arg1 arg2")

alternatively I needed more than that so I use this paramiko wrapper class (you will need to install paramiko)

from contextlib import contextmanager
import os
import re
import paramiko
import time


class SshClient:
    """A wrapper of paramiko.SSHClient"""
    TIMEOUT = 10

    def __init__(self, connection_string,**kwargs):
        self.key = kwargs.pop("key",None)
        self.client = kwargs.pop("client",None)
        self.connection_string = connection_string
        try:
            self.username,self.password,self.host = re.search("(\w+):(\w+)@(.*)",connection_string).groups()
        except (TypeError,ValueError):
            raise Exception("Invalid connection sting should be 'user:pass@ip'")
        try:
            self.host,self.port = self.host.split(":",1)
        except (TypeError,ValueError):
            self.port = "22"
        self.connect(self.host,int(self.port),self.username,self.password,self.key)
    def reconnect(self):
        self.connect(self.host,int(self.port),self.username,self.password,self.key)

    def connect(self, host, port, username, password, key=None):
        self.client = paramiko.SSHClient()
        self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        self.client.connect(host, port, username=username, password=password, pkey=key, timeout=self.TIMEOUT)

    def close(self):
        if self.client is not None:
            self.client.close()
            self.client = None

    def execute(self, command, sudo=False,**kwargs):
        should_close=False
        if not self.is_connected():
            self.reconnect()
            should_close = True
        feed_password = False
        if sudo and self.username != "root":
            command = "sudo -S -p '' %s" % command
            feed_password = self.password is not None and len(self.password) > 0
        stdin, stdout, stderr = self.client.exec_command(command,**kwargs)
        if feed_password:
            stdin.write(self.password + "\n")
            stdin.flush()

        result = {'out': stdout.readlines(),
                'err': stderr.readlines(),
                'retval': stdout.channel.recv_exit_status()}
        if should_close:
            self.close()
        return result

    @contextmanager
    def _get_sftp(self):
        yield paramiko.SFTPClient.from_transport(self.client.get_transport())

    def put_in_dir(self, src, dst):
        if not isinstance(src,(list,tuple)):
            src = [src]
        print self.execute('''python -c "import os;os.makedirs('%s')"'''%dst)
        with self._get_sftp() as sftp:
            for s in src:
                sftp.put(s, dst+os.path.basename(s))

    def get(self, src, dst):
        with self._get_sftp() as sftp:
            sftp.get(src, dst)
    def rm(self,*remote_paths):
        for p in remote_paths:
            self.execute("rm -rf {0}".format(p),sudo=True)
    def mkdir(self,dirname):
        print self.execute("mkdir {0}".format(dirname))
    def remote_open(self,remote_file_path,open_mode):
        with self._get_sftp() as sftp:
            return sftp.open(remote_file_path,open_mode)

    def is_connected(self):
        transport = self.client.get_transport() if self.client else None
        return transport and transport.is_active()

you can then use it as follows

client = SshClient("username:password@host.net")
result = client.execute("python something.py cmd1 cmd2")
print result

result2 = client.execute("cp some_file /etc/some_file",sudo=True)
print result2
Joran Beasley
  • 93,863
  • 11
  • 131
  • 160