Python Paramiko通过SFTP遍历目录

6
如何在另一台通过SSH连接的计算机上进行os.walk()操作? 问题在于os.walk()是在本地机器上执行,并且我想要ssh到另一个主机,遍历一个目录并为其中的每个文件生成MD5哈希值。

我目前编写的代码如下所示,但它没有生效。非常感谢您提供任何帮助。

try:
    hash_array = []
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect('sunbeam', port=22, username='xxxx', password='filmlight')

    spinner.start()
    for root, dirs, files in os.walk(_path):
        for file in files:
            file_path = os.path.join(os.path.abspath(root), file)
            
            #  generate hash code for file
            hash_array.append(genMD5hash(file_path))
            
            file_nb += 1
    spinner.stop()
    spinner.ok('Finished.')

    return hash_array
except Exception as e:
    print(e)
    return None
finally:
    ssh.close() 
3个回答

9

使用Paramiko递归列出目录,并使用标准文件访问接口SFTP,您需要实现一个带有SFTPClient.listdir_attr的递归函数:

from stat import S_ISDIR, S_ISREG

def listdir_r(sftp, remotedir):
    for entry in sftp.listdir_attr(remotedir):
        remotepath = remotedir + "/" + entry.filename
        mode = entry.st_mode
        if S_ISDIR(mode):
            listdir_r(sftp, remotepath)
        elif S_ISREG(mode):
            print(remotepath)

基于Python pysftp get_r在Linux上工作正常,但在Windows上不正常
另外,pysftp也实现了一个等效的os.walk功能:Connection.walktree
虽然使用SFTP协议获取远程文件的MD5可能会遇到麻烦。
尽管Paramiko通过其SFTPFile.check支持此功能,但大多数SFTP服务器(特别是最常见的SFTP/SSH服务器 - OpenSSH)不支持。 参见:
如何检查Paramiko是否成功上传文件到SFTP服务器?
在SFTP文件传输期间如何执行校验和以确保数据完整性?

所以你很可能需要使用shell md5sum命令(如果你有shell访问权限)。而且一旦你必须使用shell,考虑使用shell列出文件,因为这比通过SFTP快得多。

请参阅对目录树中的所有文件进行MD5校验
使用SSHClient.exec_command
在Python中比较下载文件的MD5与SFTP服务器上的文件

必要的警告:不要使用 AutoAddPolicy - 这样做会导致失去对 中间人攻击的保护。要获得正确的解决方案,请参见 Paramiko "未知服务器"


2
基于前面的答案,这里是一个不需要递归并使用print命令返回路径列表的版本。
from stat import S_ISDIR, S_ISREG
from collections import deque

def listdir_r(sftp, remotedir):
    dirs_to_explore = deque([remotedir])
    list_of_files = deque([])

    while len(dirs_to_explore) > 0:
        current_dir = dirs_to_explore.popleft()

        for entry in sftp.listdir_attr(current_dir):
            current_fileordir = current_dir + "/" + entry.filename

            if S_ISDIR(entry.st_mode):
                dirs_to_explore.append(current_fileordir)
            elif S_ISREG(entry.st_mode):
                list_of_files.append(current_fileordir)

    return list(list_of_files)

1

这里有另一种实现,试图模仿Python中的os.walk (python/cpython/os.py:walk)函数,以便它可以用作期望内置函数的代码的替代品。

class ParamikoWalkExample:
    def __init__(self, host, username=None):
        self._host = host
        self._username = username
        self._ssh = self._ssh_connect()
        self._sftp = self._ssh.open_sftp()

    def _ssh_connect(self):
        ssh = paramiko.SSHClient()
        ssh.load_system_host_keys()
        ssh.connect(self._host, username=self._username)
        return ssh

    def walk(
        self,
        top,
        topdown=True,
        onerror=None, # ignored
        followlinks=False,
    ):
        stack = [top]

        while stack:
            top = stack.pop()
            if isinstance(top, tuple):
                yield top
                continue

            dirs = []
            nondirs = []
            walk_dirs = []

            for entry in self._sftp.listdir_attr(top):
                if entry.st_mode is None:
                    nondirs.append(entry.filename)
                elif stat.S_ISDIR(entry.st_mode):
                    dirs.append(entry.filename)
                    walk_dirs.append(top + '/' + entry.filename)
                elif stat.S_ISREG(entry.st_mode):
                    nondirs.append(entry.filename)
                elif stat.S_ISLNK(entry.st_mode):
                    target = entry.filename
                    while True:
                        target = self._sftp.readlink(target)
                        if not target:
                            nondirs.append(entry.filename)
                            break
                        target_entry = self._sftp.stat(target)
                        if not target_entry.st_mode:
                            nondirs.append(entry.filename)
                            break

                        if stat.S_ISLNK(target_entry.st_mode):
                            continue

                        elif stat.S_ISDIR(target_entry.st_mode):
                            dirs.append(entry.filename)
                            if followlinks:
                                walk_dirs.append(top + '/' + entry.filename)
                            break
                        elif stat.S_ISREG(target_entry.st_mode):
                            nondirs.append(entry.filename)
                            break

            if topdown:
                yield top, dirs, nondirs
                for new_path in reversed(walk_dirs):
                    stack.append(new_path)
            else:
                # Yield after sub-directory traversal if going bottom up
                stack.append((top, dirs, nondirs))
                for new_path in reversed(walk_dirs):
                    stack.append(new_path)

顺便提一下:由于在深层文件层次结构中出现递归错误,内置函数最近在主分支上被重写为使用堆栈而不是递归:https://github.com/python/cpython/issues/89727


1
+1,尽管您无法在SFTP路径上使用os.path.joinos.path.join使用本地文件系统约定,而SFTP始终使用正斜杠分隔符。 因此,您的代码在Windows上将失败。 另请参见Python pysftp put_r在Windows上不起作用 - Martin Prikryl
1
不知道,谢谢!我已经将您的建议编辑到答案中了。 - siikamiika

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接