【问题标题】:Recursive directory copy with Paramiko in Python在 Python 中使用 Paramiko 进行递归目录复制
【发布时间】:2023-03-26 06:55:01
【问题描述】:

我是 Python 脚本的新手。我需要将几个文件夹从我的本地机器(Windows)复制到 Linux 服务器。截至目前,我正在通过打开 WinSCP 控制台来复制文件夹。我需要自动化这个过程。我使用 Paramiko 模块库在 Python 中编写了以下代码。

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)

以上无法正常工作并出现以下错误。你能帮我把windows路径C:\Users\Public\test中的文件夹复制到Linux服务器路径/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'

【问题讨论】:

  • 我通过链接“gist.github.com/johnfink8/2190472”解决了这个问题
  • 我无法访问存储库...但很高兴知道您已经解决了您的问题
  • 该代码留下悬空的句柄,不会关闭其连接。

标签: python sftp paramiko


【解决方案1】:

请从链接https://gist.github.com/johnfink8/2190472 中查看以下代码。我在 sn-p 中使用了put_all 方法。

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)

【讨论】:

    【解决方案2】:

    除了@user1041177 的答案之外,还有一种方法可以在您使用 windows 到 linux 主机时执行此操作(实际上不确定是哪种主机)。

    我不知道为什么,但是如果我在远程路径上保留反斜杠,我会收到 FileNotFoundException。唯一可行的方法是将所有 '\' 替换为 '/'

    也许有人可以告诉我避免这种情况的正确方法?

    如果您遇到相同的问题,这里有一部分与上面完全相同的代码可以为您提供面包屑:

    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('\\', '/'))
    

    顺便说一句,我没有在对象中使用这些函数,这就是为什么它们是“套接字”而不是“自我”,因为我通过将 SFTP 套接字传递给它们来调用这些函数。

    最后要感谢@user1041177,工作就像一个魅力。

    【讨论】:

      【解决方案3】:

      我试图从 windows 机器复制到 linux 机器并得到与上面的@Apex 相同的错误。我使用的是 put_all 方法,我必须对部分代码进行一些“替换”。

      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('\\', '/'))
      

      【讨论】:

        【解决方案4】:

        我发现上述方法有一些缺点 - 首先,推杆/吸气剂无法按您期望的方式运行 - 如果您想将 /foo/bar 放入 /some/folder,你不能,因为它不会让你将文件从源文件夹放到不同的目标文件夹 - 你唯一能做的就是把 /foo/bar 进入 /some/bar。此外,您必须将源指定为 /foo/bar 并将目标指定为 /some 以结束 /some/bar -我觉得这很令人困惑,因为这不是大多数操作系统/ftp 系统处理放置/获取/复制/等的方式。所以,我改进了上面列出的答案:

        如果您要从 Windows 转到 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)
        

        如果您只是在使用 Windows,则将 posixpath.join 换成 os.path.join 并删除 .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)
        

        try 语句的原因是如果文件夹已经存在,sftp.mkdir 就会出错。

        【讨论】:

          【解决方案5】:

          Paramiko 不支持递归操作。

          您可以使用 pysftp。它是围绕 Paramiko 的包装器,具有更多 Python 风格的外观和感觉,并支持递归操作。见

          或者您可以将您的代码基于pysftp source code。或查看我对Python pysftp get_r from Linux works fine on Linux but not on Windows 的回复。

          【讨论】:

            猜你喜欢
            • 2011-10-04
            • 1970-01-01
            • 2010-12-31
            • 1970-01-01
            • 1970-01-01
            • 2013-02-17
            • 2011-03-20
            • 2016-06-06
            相关资源
            最近更新 更多