【问题标题】:Directory transfers with Paramiko使用 Paramiko 传输目录
【发布时间】:2011-05-23 12:14:28
【问题描述】:

如何使用 Paramiko 传输完整目录? 我正在尝试使用:

sftp.put("/Folder1","/Folder2")

这给了我这个错误 -

错误:[Errno 21] 是一个目录

【问题讨论】:

    标签: python sftp paramiko


    【解决方案1】:

    您可以继承 paramiko.SFTPClient 并向其添加以下方法:

    import paramiko
    import os
    
    class MySFTPClient(paramiko.SFTPClient):
        def put_dir(self, source, target):
            ''' Uploads the contents of the source directory to the target path. The
                target directory needs to exists. All subdirectories in source are 
                created under target.
            '''
            for item in os.listdir(source):
                if os.path.isfile(os.path.join(source, item)):
                    self.put(os.path.join(source, item), '%s/%s' % (target, item))
                else:
                    self.mkdir('%s/%s' % (target, item), ignore_existing=True)
                    self.put_dir(os.path.join(source, item), '%s/%s' % (target, item))
    
        def mkdir(self, path, mode=511, ignore_existing=False):
            ''' Augments mkdir by adding an option to not fail if the folder exists  '''
            try:
                super(MySFTPClient, self).mkdir(path, mode)
            except IOError:
                if ignore_existing:
                    pass
                else:
                    raise
    

    使用它:

    transport = paramiko.Transport((HOST, PORT))
    transport.connect(username=USERNAME, password=PASSWORD)
    sftp = MySFTPClient.from_transport(transport)
    sftp.mkdir(target_path, ignore_existing=True)
    sftp.put_dir(source_path, target_path)
    sftp.close()
    

    【讨论】:

    • 这是最好的办法!
    • 你能举例说明如何使用它吗?在我实例化类后,我得到AttributeError: 'SFTPClient' object has no attribute 'send'
    • @skoll - 这实际上不起作用......我遇到与 kkmoslehpour 相同的问题
    • @tsar2512 我刚刚用 Python 2.7.14 和 3.6.5 重新测试了它,它工作正常。我还添加了用法示例。如果您仍然无法使其工作,我也许可以提供帮助,您提供代码示例。
    • 我没有看到 mode 的实用性
    【解决方案2】:

    您需要像在本地使用 python 一样执行此操作(如果您没有使用 shutils)。

    os.walk()sftp.mkdir()sftp.put() 组合在一起。您可能还需要使用 os.path.islink() 检查每个文件和目录,具体取决于您是否要解析符号链接。

    【讨论】:

    • pysftp 模块附带了该算法的实现,建立在 Paramiko 之上。见my answer
    【解决方案3】:

    这是我的一段代码:

    import errno
    import os
    import stat
    
    def download_files(sftp_client, remote_dir, local_dir):
        if not exists_remote(sftp_client, remote_dir):
            return
    
        if not os.path.exists(local_dir):
            os.mkdir(local_dir)
    
        for filename in sftp_client.listdir(remote_dir):
            if stat.S_ISDIR(sftp_client.stat(remote_dir + filename).st_mode):
                # uses '/' path delimiter for remote server
                download_files(sftp_client, remote_dir + filename + '/', os.path.join(local_dir, filename))
            else:
                if not os.path.isfile(os.path.join(local_dir, filename)):
                    sftp_client.get(remote_dir + filename, os.path.join(local_dir, filename))
    
    
    def exists_remote(sftp_client, path):
        try:
            sftp_client.stat(path)
        except IOError, e:
            if e.errno == errno.ENOENT:
                return False
            raise
        else:
            return True
    

    【讨论】:

    • 第 15 行有错字吗?是download_files 吗?除此之外工作正常
    • @Daniele 是的,这是错字,这是一个递归调用。解决了,谢谢!不知道它是如何滑到那里的:)
    【解决方案4】:

    这一切都可以使用 paramiko 轻松完成。

    以下代码的高级摘要是:
    - 连接到 SFTP(步骤 1 到 3)
    - 指定您的源和目标文件夹。 (第四步)
    - 将它们一一复制到您喜欢的任何地方(我已将它们发送到 /tmp/)。 (第 5 步)

    import paramiko
    
    # 1 - Open a transport
    host="your-host-name"
    port = port_number
    transport = paramiko.Transport((host, port))
    
    # 2 - Auth
    password="sftp_password"
    username="sftp_username"
    transport.connect(username = username, password = password)
    
    # 3 - Go!
    
    sftp = paramiko.SFTPClient.from_transport(transport)
    
    # 4 - Specify your source and target folders.
    source_folder="some/folder/path/on/sftp"
    inbound_files=sftp.listdir(source_folder)
    
    # 5 - Download all files from that path
    for file in inbound_files :
        filepath = source_folder+file
        localpath = "/tmp/"+file
        sftp.get(filepath, localpath)
    

    【讨论】:

    • 不错!当它要求“传输完整目录”时,这个问题并不完全清楚。我喜欢你从将目录下载到本地机器的角度来看待它。
    • 第五步有错别字,source_folde 而不是应该的source_folder
    【解决方案5】:

    您可以用 paramiko 的替换 sftp = self.client.open_sftp() 并在此处删除 libcloud

    import os.path
    from stat import S_ISDIR
    from libcloud.compute.ssh import SSHClient
    from paramiko.sftp import SFTPError
    
    class CloudSSHClient(SSHClient):
    
    
        @staticmethod
        def normalize_dirpath(dirpath):
            while dirpath.endswith("/"):
                dirpath = dirpath[:-1]
            return dirpath
    
    
        def mkdir(self, sftp, remotepath, mode=0777, intermediate=False):
            remotepath = self.normalize_dirpath(remotepath)
            if intermediate:
                try:
                    sftp.mkdir(remotepath, mode=mode)
                except IOError, e:
                    self.mkdir(sftp, remotepath.rsplit("/", 1)[0], mode=mode,
                               intermediate=True)
                    return sftp.mkdir(remotepath, mode=mode)
            else:
                sftp.mkdir(remotepath, mode=mode)
    
    
        def put_dir_recursively(self,  localpath, remotepath, preserve_perm=True):
            "upload local directory to remote recursively"
    
            assert remotepath.startswith("/"), "%s must be absolute path" % remotepath
    
            # normalize
            localpath = self.normalize_dirpath(localpath)
            remotepath = self.normalize_dirpath(remotepath)
    
            sftp = self.client.open_sftp()
    
            try:
                sftp.chdir(remotepath)
                localsuffix = localpath.rsplit("/", 1)[1]
                remotesuffix = remotepath.rsplit("/", 1)[1]
                if localsuffix != remotesuffix:
                    remotepath = os.path.join(remotepath, localsuffix)
            except IOError, e:
                pass
    
            for root, dirs, fls in os.walk(localpath):
                prefix = os.path.commonprefix([localpath, root])
                suffix = root.split(prefix, 1)[1]
                if suffix.startswith("/"):
                    suffix = suffix[1:]
    
                remroot = os.path.join(remotepath, suffix)
    
                try:
                    sftp.chdir(remroot)
                except IOError, e:
                    if preserve_perm:
                        mode = os.stat(root).st_mode & 0777
                    else:
                        mode = 0777
                    self.mkdir(sftp, remroot, mode=mode, intermediate=True)
                    sftp.chdir(remroot)
    
                for f in fls:
                    remfile = os.path.join(remroot, f)
                    localfile = os.path.join(root, f)
                    sftp.put(localfile, remfile)
                    if preserve_perm:
                        sftp.chmod(remfile, os.stat(localfile).st_mode & 0777)
    

    【讨论】:

    • 很好,完整的答案。只是一些小问题:我建议使用os.path.split 而不是str.rsplit;另外,您定义了一个normalize_path 方法,然后在put_dir_recursively 中执行suffix = suffix[1:]
    【解决方案6】:

    为我做这样的事情,所有文件夹和文件都复制到远程服务器。

    parent = os.path.expanduser("~")
    for dirpath, dirnames, filenames in os.walk(parent):
        remote_path = os.path.join(remote_location, dirpath[len(parent)+1:])
            try:
                ftp.listdir(remote_path)
            except IOError:
                ftp.mkdir(remote_path)
    
            for filename in filenames:
                ftp.put(os.path.join(dirpath, filename), os.path.join(remote_path, filename))
    

    【讨论】:

      【解决方案7】:

      我认为你做不到。查找 os.walk 的文档并“手动”复制每个文件。

      【讨论】:

        【解决方案8】:

        这是我的第一个 StackOverflow 答案。我今天有一个与此类似的任务。因此,我试图找到一种使用 python 和 paramiko 将整个文件夹从 windows 复制到 linux 的直接方法。经过一番研究,我想出了这个解决方案,它适用于包含子文件夹和文件的较小大小的文件夹。

        此解决方案首先为当前文件夹制作 zip 文件(os.walk() 在这里非常有用),然后复制到目标服务器并在那里解压缩。

        zipHere = zipfile.ZipFile("file_to_copy.zip", "w")
        
        for root, folders, files in os.walk(FILE_TO_COPY_PATH):
            for file in files:
                zipHere.write(os.path.join(root, file), arcname=os.path.join(os.path.relpath(root, os.path.dirname(FILE_TO_COPY_PATH)), file))
            for folder in folders:
                zipHere.write(os.path.join(root, folder), arcname=os.path.join(os.path.relpath(root, os.path.dirname(FILE_TO_COPY_PATH)), folder))
        zipHere.close()
        
        # sftp is the paramiko.SFTPClient connection
        sftp.put('local_zip_file_location','remote_zip_file_location')
        
        # telnet_conn is the telnetlib.Telnet connection
        telnet_conn.write('cd cd_to_zip_file_location')
        telnet_conn.write('unzip -o file_to_copy.zip')
        

        【讨论】:

          【解决方案9】:

          据我所知,Paramiko 不支持递归文件上传。但是,我找到了solution for recursive upload using Paramiko here。以下是他们递归上传功能的摘录:

             def _send_recursive(self, files):
                  for base in files:
                      lastdir = base
                      for root, dirs, fls in os.walk(base):
                          # pop back out to the next dir in the walk
                          while lastdir != os.path.commonprefix([lastdir, root]):
                              self._send_popd()
                              lastdir = os.path.split(lastdir)[0]
                          self._send_pushd(root)
                          lastdir = root
                          self._send_files([os.path.join(root, f) for f in fls])
          

          您可以尝试使用他们的函数SCPClient.put调用上述函数进行递归上传或自己实现。

          【讨论】:

          • os.walk() 是解决这个问题的正确方法,但不要完全复制它,因为它以特定于 SCP 的方式处理事情。 SFTP 的工作方式有点不同(免责声明,我编写了该代码)
          • @Martin Kosek - 我喜欢你的回答,但看起来你的解决方案链接已损坏。你能编辑和修复吗?谢谢。
          • @RobertMS - 对,我看到 Python 模块已被删除。在这种情况下,我认为 JimB 的解决方案是最好的——结合 os.walk()、sftp.mkdir() 和 sftp.put() 来实现目标。
          【解决方案10】:

          Paramiko 本身不支持目录传输。正如这里的许多现有答案所示,您必须实施它。

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

          或者您可以将您的代码基于pysftp source code。完整的独立便携式 Paramiko-only 代码请参阅我的答案:

          正如我上面的回答所示,如果你在 Windows 上,你实际上必须使用自己的代码,因为 pysftp 在那里不起作用。

          【讨论】:

            【解决方案11】:

            我的回答和上面的差不多,就是列个单子,然后一个一个转过来。

            import paramiko
            
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(hostname='11.11.11.1111', username='root', password='********', port=22)
            sftp_client = ssh.open_sftp()
            source_folder = '/var/ftp/file_pass'
            local_folder = 'C:/temp/file_pass'
            inbound_files = sftp_client.listdir(source_folder)
            print(inbound_files)
            
            for ele in inbound_files:
                try:
                    path_from = source_folder + '/' + ele
                    path_to = local_folder + '/'+ ele
                    sftp_client.get(path_from, path_to)
                except:
                    print(ele)
            
            sftp_client.close()
            ssh.close()
            

            【讨论】:

            • 好吧,正如您的回答所说,类似的代码已经发布了几次。那么为什么要发布另一个相同的变体呢?
            • 在某些时候我的回答更容易理解,因为不是每个看到的人都是 python 专家。他们需要查看一些简化的代码。此外,并非所有发布的代码都可以正常工作,至少我尝试使用我的代码。这个对我有用。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-10-04
            • 1970-01-01
            相关资源
            最近更新 更多