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