paramiko批量上传下载sftp,解决访问windows系列sftp乱码问题

一、封装代码

import os
import paramiko
from stat import S_ISDIR
import shutil


class ConnectSftp(object):
    def __init__(self,ip,port,username,password):
        self.ip=ip
        self.port=port
        self.username=username
        self.password=password
        self.connect()

    def connect(self):
        self.transport = paramiko.Transport((self.ip, self.port))
        self.transport.connect(username=self.username, password=self.password)
        self.sftp = paramiko.SFTPClient.from_transport(self.transport)

    def close(self):
        self.transport.close()

    def isdir(self,path):
        try:
            return S_ISDIR(self.sftp.stat(path).st_mode)
        except IOError:
            return False

    def sftp_rm(self,path):
        files = self.sftp.listdir(path=path)
        for f in files:
            filepath = os.path.join(path, f)
            if self.isdir(filepath):
                self.sftp_rm(filepath)
            else:
                self.sftp.remove(filepath)
        self.sftp.rmdir(path)

    def local_to_sftp(self,local_dir_name,remote_dir_name,move_state=False):
        '''
        :param local_dir_name: 本地文件夹路径
        :param remote_dir_name: sftp文件夹路径
        :param move_state: 是否删除本地文件夹
        :return:
        '''
        if os.path.isdir(local_dir_name):
            # 文件夹,不能直接下载,需要继续循环
            self.check_sftp_dir(remote_dir_name)
            for remote_file_name in os.listdir(local_dir_name):
                sub_local = os.path.join(local_dir_name, remote_file_name)
                sub_local = sub_local.replace('\\', '/')
                sub_remote = os.path.join(remote_dir_name, remote_file_name)
                sub_remote = sub_remote.replace('\\', '/')
                print(sub_local, sub_remote)
                self.local_to_sftp(sub_local, sub_remote)
        else:
            # 文件,直接上传
            print('开始上传文件:' + local_dir_name)
            self.sftp.put(local_dir_name, remote_dir_name)
        if move_state:
            shutil.rmtree(local_dir_name)

    def sftp_to_local(self,remote_dir_name,local_dir_name,move_state=False):
        '''
        :param remote_dir_name: sftp文件夹路径
        :param local_dir_name: 本地文件夹路径
        :param move_state: 是否删除sftp文件夹
        :return:
        '''
        try:
            self.sftp.stat(remote_dir_name)
        except Exception:
            raise BaseException({"error":"sftp路径不存在"})
        remote_file = self.sftp.stat(remote_dir_name)
        if S_ISDIR(remote_file.st_mode):
            # 文件夹,不能直接下载,需要继续循环
            self.check_local_dir(local_dir_name)
            print('开始下载文件夹:' + remote_dir_name)
            for remote_file_name in self.sftp.listdir(remote_dir_name):
                sub_remote = os.path.join(remote_dir_name, remote_file_name)
                sub_remote = sub_remote.replace('\\', '/')
                sub_local = os.path.join(local_dir_name, remote_file_name)
                sub_local = sub_local.replace('\\', '/')
                self.sftp_to_local(sub_remote, sub_local)
        else:
            # 文件,直接下载
            print('开始下载文件:' + remote_dir_name)
            self.sftp.get(remote_dir_name, local_dir_name)
        if move_state:
            self.sftp_rm(remote_dir_name)


    def check_local_dir(self, local_dir_name):
        """本地文件夹是否存在,不存在则创建"""
        if not os.path.exists(local_dir_name):
            os.makedirs(local_dir_name)

    def check_sftp_dir(self, remote_dir_name):
        """sftp文件夹是否存在,不存在则创建"""
        try:
            self.sftp.stat(remote_dir_name)
        except IOError as e:
            self.sftp.mkdir(remote_dir_name)

    def cmd(self, command):
        ssh = paramiko.SSHClient()
        ssh._transport = self.transport
        stdin, stdout, stderr = ssh.exec_command(command)
        print(stdout)
        result = stdout.read()
        return result


cs=ConnectSftp('ip',port,'name','pwd')
if __name__ == '__main__':
    cs.local_to_sftp(r"本地路径",'sftp路径',move_state=True)
    cs.sftp_to_local("sftp路径",r"本地路径",move_state=True)
    cs.close()

二、解决windows2008-sftp文件路径乱码

找到paramiko下的py3compat文件,将以下4个函数内的编码改成‘gbk’

    def b(s, encoding="gbk"):
        """cast unicode or bytes to bytes"""
        if isinstance(s, bytes):
            return s
        elif isinstance(s, str):
            return s.encode(encoding)
        else:
            raise TypeError("Expected unicode or bytes, got {!r}".format(s))

    def u(s, encoding="gbk"):
        """cast bytes or unicode to unicode"""
        if isinstance(s, bytes):
            return s.decode(encoding)
        elif isinstance(s, str):
            return s
        else:
            raise TypeError("Expected unicode or bytes, got {!r}".format(s))

def b(s, encoding="gbk"): # NOQA """cast unicode or bytes to bytes""" if isinstance(s, str): return s elif isinstance(s, unicode): # NOQA return s.encode(encoding) elif isinstance(s, buffer): # NOQA return s else: raise TypeError("Expected unicode or bytes, got {!r}".format(s)) def u(s, encoding="gbk"): # NOQA """cast bytes or unicode to unicode""" if isinstance(s, str): return s.decode(encoding) elif isinstance(s, unicode): # NOQA return s elif isinstance(s, buffer): # NOQA return s.decode(encoding) else: raise TypeError("Expected unicode or bytes, got {!r}".format(s))

三、不修改源码解决乱码问题

import os
import paramiko
from stat import S_ISDIR
from paramiko.sftp_attr import SFTPAttributes
from paramiko.ssh_exception import SSHException
from paramiko.sftp import CMD_STATUS
from paramiko.common import DEBUG
from paramiko.py3compat import long
from paramiko.message import Message
import shutil



#遍历文件夹删除文件
def traversing_dir(rootDir):
    #遍历根目录
    for root,dirs,files in os.walk(rootDir):
        for file in files:
            r_file=os.path.join(root,file)
            if os.path.isfile(r_file):
                os.remove(r_file)    #删除文件
        for dir in dirs:
            #递归调用自身
            traversing_dir(dir)

b_slash = b"/"

# 设置服务器的编码,如果服务器是windows的用gbk,是linux用utf-8
def b(s, encoding="gbk"):
    """cast unicode or bytes to bytes"""
    if isinstance(s, bytes):
        return s
    elif isinstance(s, str):
        return s.encode(encoding)
    else:
        raise TypeError("Expected unicode or bytes, got {!r}".format(s))

# 设置服务器的编码,如果服务器是windows的用gbk,是linux用utf-8
def u(s, encoding="gbk"):
    """cast bytes or unicode to unicode"""
    if isinstance(s, bytes):
        return s.decode(encoding)
    elif isinstance(s, str):
        return s
    else:
        raise TypeError("Expected unicode or bytes, got {!r}".format(s))


class NEWMessage(Message):
    def get_text(self):
        """
        Fetch a Unicode string from the stream.
        """
        return u(self.get_string())


class NewSFTPClient(paramiko.SFTPClient):

    def _adjust_cwd(self, path):
        """
        Return an adjusted path if we're emulating a "current working
        directory" for the server.
        """
        path = b(path)
        if self._cwd is None:
            return path
        if len(path) and path[0:1] == b_slash:
            # absolute path
            return path
        if self._cwd == b_slash:
            return self._cwd + path
        return self._cwd + b_slash + path

    def _async_request(self, fileobj, t, *arg):
        # this method may be called from other threads (prefetch)
        self._lock.acquire()
        try:
            msg = NEWMessage()
            msg.add_int(self.request_number)
            for item in arg:
                if isinstance(item, long):
                    msg.add_int64(item)
                elif isinstance(item, int):
                    msg.add_int(item)
                elif isinstance(item, SFTPAttributes):
                    item._pack(msg)
                else:
                    # For all other types, rely on as_string() to either coerce
                    # to bytes before writing or raise a suitable exception.
                    msg.add_string(item)
            num = self.request_number
            self._expecting[num] = fileobj
            self.request_number += 1
        finally:
            self._lock.release()
        self._send_packet(t, msg)
        return num

    def _read_response(self, waitfor=None):
        while True:
            try:
                t, data = self._read_packet()
            except EOFError as e:
                raise SSHException("Server connection dropped: {}".format(e))
            msg = NEWMessage(data)
            num = msg.get_int()
            self._lock.acquire()
            try:
                if num not in self._expecting:
                    # might be response for a file that was closed before
                    # responses came back
                    self._log(DEBUG, "Unexpected response #{}".format(num))
                    if waitfor is None:
                        # just doing a single check
                        break
                    continue
                fileobj = self._expecting[num]
                del self._expecting[num]
            finally:
                self._lock.release()
            if num == waitfor:
                # synchronous
                if t == CMD_STATUS:
                    self._convert_status(msg)
                return t, msg

            # can not rewrite this to deal with E721, either as a None check
            # nor as not an instance of None or NoneType
            if fileobj is not type(None):  # noqa
                fileobj._async_response(t, msg, num)
            if waitfor is None:
                # just doing a single check
                break
        return None, None


class ConnectSftp(object):
    def __init__(self, ip, port, username, password):
        self.ip = ip
        self.port = port
        self.username = username
        self.password = password
        self.connect()

    def connect(self):
        self.transport = paramiko.Transport((self.ip, self.port))
        self.transport.connect(username=self.username, password=self.password)
        self.sftp = NewSFTPClient.from_transport(self.transport)

    def close(self):
        self.transport.close()

    def isdir(self, path):
        try:
            return S_ISDIR(self.sftp.stat(path).st_mode)
        except IOError:
            return False

    def sftp_rm(self, path):
        files = self.sftp.listdir(path=path)
        for f in files:
            filepath = os.path.join(path, f)
            if self.isdir(filepath):
                self.sftp_rm(filepath)
            else:
                self.sftp.remove(filepath)
        # self.sftp.rmdir(path)

    def local_to_sftp(self, local_dir_name, remote_dir_name, move_state=False):
        '''
        :param local_dir_name: 本地文件夹路径
        :param remote_dir_name: sftp文件夹路径
        :param move_state: 是否删除本地文件夹
        :return:
        '''
        if os.path.isdir(local_dir_name):
            # 文件夹,不能直接下载,需要继续循环
            self.check_sftp_dir(remote_dir_name)
            for remote_file_name in os.listdir(local_dir_name):
                sub_local = os.path.join(local_dir_name, remote_file_name)
                sub_local = sub_local.replace('\\', '/')
                sub_remote = os.path.join(remote_dir_name, remote_file_name)
                sub_remote = sub_remote.replace('\\', '/')
                self.local_to_sftp(sub_local, sub_remote)
        else:
            # 文件,直接上传
            print('开始上传文件:' + local_dir_name)
            print(remote_dir_name)
            self.sftp.put(local_dir_name, remote_dir_name)
        if move_state:
            traversing_dir(local_dir_name)

    def sftp_to_local(self, remote_dir_name, local_dir_name, move_state=False):
        '''
        :param remote_dir_name: sftp文件夹路径
        :param local_dir_name: 本地文件夹路径
        :param move_state: 是否删除sftp文件夹
        :return:
        '''
        try:
            self.sftp.stat(remote_dir_name)
        except Exception:
            raise BaseException({"error": "sftp路径不存在:{}".format(remote_dir_name)})
        remote_file = self.sftp.stat(remote_dir_name)
        if S_ISDIR(remote_file.st_mode):
            # 文件夹,不能直接下载,需要继续循环
            self.check_local_dir(local_dir_name)
            print('开始下载文件夹:' + remote_dir_name)
            for remote_file_name in self.sftp.listdir(remote_dir_name):
                sub_remote = os.path.join(remote_dir_name, remote_file_name)
                sub_remote = sub_remote.replace('\\', '/')
                sub_local = os.path.join(local_dir_name, remote_file_name)
                sub_local = sub_local.replace('\\', '/')
                self.sftp_to_local(sub_remote, sub_local)
        else:
            # 文件,直接下载
            print('开始下载文件:' + remote_dir_name)
            self.sftp.get(remote_dir_name, local_dir_name)
        if move_state:
            self.sftp_rm(remote_dir_name)

    def check_local_dir(self, local_dir_name):
        """本地文件夹是否存在,不存在则创建"""
        if not os.path.exists(local_dir_name):
            os.makedirs(local_dir_name)

    def check_sftp_dir(self, remote_dir_name):
        """sftp文件夹是否存在,不存在则创建"""
        try:
            self.sftp.stat(remote_dir_name)
        except IOError as e:
            self.sftp.mkdir(remote_dir_name)

    def cmd(self, command):
        ssh = paramiko.SSHClient()
        ssh._transport = self.transport
        stdin, stdout, stderr = ssh.exec_command(command)
        result = stdout.read()
        return result

 

posted @ 2020-04-09 23:16  Maple_feng  阅读(1571)  评论(0编辑  收藏  举报