7

I am new to Python scripting. I need to copy few folders from my local machine (windows) to Linux server. As of now, I am copying the folders by opening WinSCP console. I need to automate this process. I have written a below code in Python using Paramiko module library.

import paramiko
import os
transport = paramiko.Transport(('10.10.10.10', 22))
transport.connect(username='weblogic', password='weblogic')
sftp = paramiko.SFTPClient.from_transport(transport)
filepath = '/apps/logs'
localpath = 'C:\\Users\\Public\\test'
sftp.put(localpath,filepath)

Above is not working properly and giving below error. Can you please help me to copy the folder present in the windows path C:\Users\Public\test to Linux server path /apps/logs?

Traceback (most recent call last):
  File "C:\Users\Desktop\python\execute_script.py", line 28, in <module>
    sftp.put(localpath,filepath)
  File "C:\Python27\lib\paramiko\sftp_client.py", line 548, in put
    fl = file(localpath, 'rb')
IOError: [Errno 13] Permission denied: 'C:\\Users\\Public\\test'
3
  • i solved the issue by following the link "gist.github.com/johnfink8/2190472" Commented Feb 18, 2014 at 9:30
  • I can't access to the repository... but good to know you already solved your question Commented Feb 18, 2014 at 9:37
  • That code leaves dangling handles, doesn't close its connections. Commented May 18, 2020 at 6:39

5 Answers 5

12

Please check the below code from the link https://gist.github.com/johnfink8/2190472. I have used put_all method in the snippet.

import paramiko
import socket
import os
from stat import S_ISDIR

class SSHSession(object):
    # Usage:
    # Detects DSA or RSA from key_file, either as a string filename or a
    # file object.  Password auth is possible, but I will judge you for 
    # using it. So:
    # ssh=SSHSession('targetserver.com','root',key_file=open('mykey.pem','r'))
    # ssh=SSHSession('targetserver.com','root',key_file='/home/me/mykey.pem')
    # ssh=SSHSession('targetserver.com','root','mypassword')
    # ssh.put('filename','/remote/file/destination/path')
    # ssh.put_all('/path/to/local/source/dir','/path/to/remote/destination')
    # ssh.get_all('/path/to/remote/source/dir','/path/to/local/destination')
    # ssh.command('echo "Command to execute"')

    def __init__(self,hostname,username='root',key_file=None,password=None):
        #
        #  Accepts a file-like object (anything with a readlines() function)  
        #  in either dss_key or rsa_key with a private key.  Since I don't 
        #  ever intend to leave a server open to a password auth.
        #
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.connect((hostname,22))
        self.t = paramiko.Transport(self.sock)
        self.t.start_client()
        keys = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
        key = self.t.get_remote_server_key()
        # supposed to check for key in keys, but I don't much care right now to find the right notation
        if key_file is not None:
            if isinstance(key,str):
                key_file=open(key,'r')
            key_head=key_file.readline()
            key_file.seek(0)
            if 'DSA' in key_head:
                keytype=paramiko.DSSKey
            elif 'RSA' in key_head:
                keytype=paramiko.RSAKey
            else:
                raise Exception("Can't identify key type")
            pkey=keytype.from_private_key(key_file)
            self.t.auth_publickey(username, pkey)
        else:
            if password is not None:
                self.t.auth_password(username,password,fallback=False)
            else: raise Exception('Must supply either key_file or password')
        self.sftp=paramiko.SFTPClient.from_transport(self.t)

    def command(self,cmd):
        #  Breaks the command by lines, sends and receives 
        #  each line and its output separately
        #
        #  Returns the server response text as a string

        chan = self.t.open_session()
        chan.get_pty()
        chan.invoke_shell()
        chan.settimeout(20.0)
        ret=''
        try:
            ret+=chan.recv(1024)
        except:
            chan.send('\n')
            ret+=chan.recv(1024)
        for line in cmd.split('\n'):
            chan.send(line.strip() + '\n')
            ret+=chan.recv(1024)
        return ret

    def put(self,localfile,remotefile):
        #  Copy localfile to remotefile, overwriting or creating as needed.
        self.sftp.put(localfile,remotefile)

    def put_all(self,localpath,remotepath):
        #  recursively upload a full directory
        os.chdir(os.path.split(localpath)[0])
        parent=os.path.split(localpath)[1]
        for walker in os.walk(parent):
            try:
                self.sftp.mkdir(os.path.join(remotepath,walker[0]))
            except:
                pass
            for file in walker[2]:
                self.put(os.path.join(walker[0],file),os.path.join(remotepath,walker[0],file))

    def get(self,remotefile,localfile):
        #  Copy remotefile to localfile, overwriting or creating as needed.
        self.sftp.get(remotefile,localfile)

    def sftp_walk(self,remotepath):
        # Kindof a stripped down  version of os.walk, implemented for 
        # sftp.  Tried running it flat without the yields, but it really
        # chokes on big directories.
        path=remotepath
        files=[]
        folders=[]
        for f in self.sftp.listdir_attr(remotepath):
            if S_ISDIR(f.st_mode):
                folders.append(f.filename)
            else:
                files.append(f.filename)
        print (path,folders,files)
        yield path,folders,files
        for folder in folders:
            new_path=os.path.join(remotepath,folder)
            for x in self.sftp_walk(new_path):
                yield x

    def get_all(self,remotepath,localpath):
        #  recursively download a full directory
        #  Harder than it sounded at first, since paramiko won't walk
        #
        # For the record, something like this would gennerally be faster:
        # ssh user@host 'tar -cz /source/folder' | tar -xz

        self.sftp.chdir(os.path.split(remotepath)[0])
        parent=os.path.split(remotepath)[1]
        try:
            os.mkdir(localpath)
        except:
            pass
        for walker in self.sftp_walk(parent):
            try:
                os.mkdir(os.path.join(localpath,walker[0]))
            except:
                pass
            for file in walker[2]:
                self.get(os.path.join(walker[0],file),os.path.join(localpath,walker[0],file))
    def write_command(self,text,remotefile):
        #  Writes text to remotefile, and makes remotefile executable.
        #  This is perhaps a bit niche, but I was thinking I needed it.
        #  For the record, I was incorrect.
        self.sftp.open(remotefile,'w').write(text)
        self.sftp.chmod(remotefile,755)
Sign up to request clarification or add additional context in comments.

Comments

0

In addition to the answer @user1041177, but here a way to do it when you are on windows to linux host (not really sure which kind of host actually).

I don't know why, but if I keep backslash onto remote path, I get a FileNotFoundException. The only way to work was to replace all '\' by '/'

Maybe someone could tell me the proper way to avoid this situation at all ?

Here a part of the exact same code above to give you breadcrumbs if you encounter the same issue :

def sftp_walk(socket, remotepath):
    remotepath = remotepath.replace('\\', '/')
    path = remotepath
    files = []
    folders = []
    for f in socket.listdir_attr(remotepath.replace('\\', '/')):
        if S_ISDIR(f.st_mode):
            folders.append(f.filename)
        else:
            files.append(f.filename)
    print(path, folders, files)
    yield path, folders, files
    for folder in folders:
        new_path = os.path.join(remotepath.replace('\\', '/'), folder)
        for x in sftp_walk(socket, new_path):
            yield x


def get_all(socket, remotepath, localpath):
    remotepath = remotepath.replace('\\', '/')
    socket.chdir(os.path.split(remotepath)[0])
    parent = os.path.split(remotepath)[1]
    try:
        os.mkdir(localpath)
    except:
        pass
    for walker in sftp_walk(socket, parent):
        try:
            os.mkdir(os.path.join(localpath, walker[0]).replace('\\', '/'))
        except:
            pass
        for file in walker[2]:
            socket.get(os.path.join(walker[0], file).replace('\\', '/'), os.path.join(localpath, walker[0], file).replace('\\', '/'))

BTW, I am not using those function inside an object, that's why their is 'socket' instead of 'self' because I call those function by passing the SFTP socket to them.

Finally have to say thank you to @user1041177, working like a charm.

Comments

0

I was trying to copy from a windows box to a linux box and got the same error as @Apex above. I was using the put_all method and I had to do some "replace" on parts of the code.

def put_all(self,localpath,remotepath):
        remotepath = remotepath.replace('\\', '/')
        #  recursively upload a full directory
        os.chdir(os.path.split(localpath)[0])
        parent=os.path.split(localpath)[1]
        for walker in os.walk(parent):
            try:
                self.sftp.mkdir(os.path.join(remotepath,walker[0]).replace('\\', '/'))
            except:
                pass
            for file in walker[2]:
                self.put(os.path.join(walker[0],file).replace('\\', '/'),os.path.join(remotepath,walker[0],file).replace('\\', '/'))

Comments

0

I found a few shortcomings with the above methods - first, the putter/getter doesn't function in the way you'd expect - if you want to put /foo/bar into /some/folder, you can't as it won't let you put files from a source folder to a different destination folder - the only thing you can do is put /foo/bar into /some/bar. In addition, you have to specify the source as /foo/bar and the destination as /some to end up with /some/bar - I find this confusing as it's not how most operating/ftp systems handle putting/getting/copying/etc. So, I improved on the answers listed above:

If you're going from Windows to Linux:

def put_dir(source, dest):
    source = os.path.expandvars(source).rstrip('\\').rstrip('/')
    dest = os.path.expandvars(dest).rstrip('\\').rstrip('/')

    for root, dirs, files in os.walk(source):
        for dir in dirs:
            try:
                sftp.mkdir(posixpath.join(dest, ''.join(root.rsplit(source))[1:].replace('\\', '/'), dir))
            except:
                pass
        for file in files:
            sftp.put(os.path.join(root, file), posixpath.join(dest, ''.join(root.rsplit(source))[1:].replace('\\', '/'), file))


source = '%USERPROFILE%\\Downloads\\'
dest = '/foo/bar'

put_dir(source, dest)

If you're just doing Windows then swap out posixpath.join with os.path.join and remove .replace('\\', '/'):

def put_dir(source, dest):
    source = os.path.expandvars(source).rstrip('\\').rstrip('/')
    dest = os.path.expandvars(dest).rstrip('\\').rstrip('/')

    for root, dirs, files in os.walk(source):
        for dir in dirs:
            try:
                sftp.mkdir(os.path.join(dest, ''.join(root.rsplit(source))[1:], dir))
            except:
                pass
        for file in files:
            sftp.put(os.path.join(root, file), os.path.join(dest, ''.join(root.rsplit(source))[1:], file))


source = '%USERPROFILE%\\Downloads\\'
dest = 'foo\\bar'

put_dir(source, dest)

The reason for the try statement is that sftp.mkdir errors out if the folder already exists.

Comments

0

Paramiko does not support recursive operations.

You can use pysftp. It's a wrapper around Paramiko that has more Python-ish look and feel and supports recursive operations. See

Or you can just base your code on pysftp source code. Or see my answer to Python pysftp get_r from Linux works fine on Linux but not on Windows.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.