pythonbashsshat-job

Run script with arguments via ssh with at command from python script


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?


Solution

  • 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