拿来即用的 Python SSH+SFTP 实现类

2023-11-16

一个拿来即用的 Python SSH 和 SFTP 实现类,可用于:

  • 与 Linux 服务器建立持续交互的 SSH 会话
  • 从 Linux 服务器下载远程文件
  • 上传本地文件到 Linux 服务器

新创建一个 linux_client.py 文件,编写下面的 LinuxClient 类代码:

import re
import time
# 通过 pip install paramiko 命令安装 paramiko 库
from paramiko import SSHClient, RSAKey, AutoAddPolicy, ssh_exception

class LinuxClient():
    def __init__(
        self,
        hostname: str,
        port: int = 22,
        private_key_file: str = None,
        username: str = None,
        password: str = None,
        tcp_timeout: int = 15,
    ) -> None:
        """初始化 LinuxClient 实例对象

        按以下优先级选择身份认证的方式:
            - 使用 `username` 和 `private_key_file` 认证
            - 使用 `username` 和 `password` 认证

        Args:
            hostname (str): 连接的服务器
            port (int, optional): 连接的服务器端口. 默认值为 22.
            private_key_file (str, optional): 认证私钥文件路径. 默认值为 None.
            username (str, optional): 认证用户名. 默认值为 None.
            password (str, optional): 认证密码. 默认值为 None.
            tcp_timeout (int, optional): TCP连接的超时秒数. 默认值为 15.
        """
        self.hostname = hostname
        self.port = port
        self.__ssh_transport = SSHClient()  # paramiko.client.SSHClient
        # 首次连接时自动信任远程机器, 建立 SSH 互信通道
        self.__ssh_transport.set_missing_host_key_policy(AutoAddPolicy())
        self.__ssh_session = None  # paramiko.channel.Channel
        self.__sftp = None  # paramiko.sftp_client.SFTPClient
        self._init_results = (True, '初始化成功')
        if not username:
            self._init_results = (False, '认证用户名不能为空')
            return
        try:
            self.__create_ssh_connect(username, private_key_file, password, tcp_timeout)
        except Exception as e:
            self._init_results = (False, str(e))
        if self._init_results[0]:
            # 建立持续交互的 SSH 会话
            self.__ssh_session = self.__ssh_transport.get_transport().open_session()
            self.__ssh_session.get_pty()
            self.__ssh_session.invoke_shell()
            # 使用 SSH 连接创建 SFTP 连接
            self.__sftp = self.__ssh_transport.open_sftp()

    def __create_ssh_connect(self, username, private_key_file, password, tcp_timeout):
        """建立 SSH 连接并建立会话"""
        try:
            if private_key_file:  # 私钥认证
                self.__ssh_transport.connect(
                    self.hostname,
                    port=self.port,
                    username=username,
                    pkey=RSAKey.from_private_key_file(private_key_file),  # 导入私钥
                    banner_timeout=60,  # 等待 SSHBanner 出现的超时秒数
                    timeout=tcp_timeout,
                )
            elif password:  # 用户名密码认证
                self.__ssh_transport.connect(
                    self.hostname,
                    port=self.port,
                    username=username,
                    password=password,
                    banner_timeout=60,
                    timeout=tcp_timeout,
                )
            else:
                self._init_results = (False, '认证用的参数不完整')
        except FileNotFoundError:
            self._init_results = (False, '认证私钥文件不存在')
        except TimeoutError:
            self._init_results = (False, '服务器网络连接超时')
        except ssh_exception.AuthenticationException:
            self._init_results = (False, '服务器身份认证失败')

    def _shell_cache(self):
        """输出 Shell 缓存的格式化文本"""
        result = ''
        while True:
            time.sleep(0.5)
            res = self.__ssh_session.recv(65535).decode('utf8')
            result += res
            if res.endswith('# ') or res.endswith('$ '):
                break
        result = re.sub('\x1b.*?m', '', result)  # 移除 `\xblah[0m` 等无效内容
        return result.strip('\n')  # 移除换行

    def run(self, command: bytes) -> str:
        """执行 Shell 命令, 持续交互

        Args:
            command (bytes): 要发送的命令数据

        Returns:
            str: 格式化后的 Shell 缓存
        """
        channel = self.__ssh_session  # 获取通道
        channel.send(f'{command}\n')  # 执行命令
        return self._shell_cache()

    @staticmethod
    def upload_callback(current_bytes: int, total_bytes: int):
        """上传回调函数 (如果需要回调,可以在继承时重写这个函数)

        Args:
            current_bytes (int): 目前传输的字节数
            total_bytes (int): 要传输的总字节数
        """
        # print(f'{current_bytes}/{total_bytes}')
        pass

    def upload(self, local_path: str, target_path: str):
        """将本地文件从本地主机上传到远程服务器

        Args:
            local_path (str): 要上传的本地文件路径
            target_path (str): 远程服务器上的存储路径, 包含文件名

        Returns:
            tuple: (result: bool, describe: str)
        """
        try:
            self.__sftp.put(
                localpath=local_path,
                remotepath=target_path,
                callback=LinuxClient.upload_callback,
                confirm=True,
            )
            # 增加权限让其他用户可读可执行, 用 0o 作为前缀表示八进制
            self.__sftp.chmod(target_path, 0o755)
        except FileNotFoundError:
            return (False, '本地或远程路径无效')
        else:
            return (True, '本地文件上传成功')

    @staticmethod
    def download_callback(current_bytes: int, total_bytes: int):
        """下载回调函数 (如果需要回调,可以在继承时重写这个函数)

        Args:
            current_bytes (int): 目前传输的字节数
            total_bytes (int): 要传输的总字节数
        """
        # print(f'{current_bytes}/{total_bytes}')
        pass

    def download(self, target_path: str, local_path: str):
        """将远程文件从远程服务器下载到本地

        Args:
            target_path (str): 要下载的远程文件路径
            local_path (str): 本地主机上的存储路径

        Returns:
            tuple: (result: bool, describe: str)
        """
        try:
            self.__sftp.get(
                remotepath=target_path,
                localpath=local_path,
                callback=LinuxClient.download_callback,
                prefetch=True,
            )
        except FileNotFoundError:
            return (False, '远程或本地路径无效')
        else:
            return (True, '远程文件下载成功')

    def __del__(self):
        """释放 LinuxClient 实例对象"""
        if self._init_results[0]:
            self.__sftp.close()
            self.__ssh_session.close()
        self.__ssh_transport.close()

然后是调用上面 LinuxClient 类的示例代码:

from linux_client import LinuxClient

lc = LinuxClient(
    hostname='123.45.67.89',
    port=34567,
    username='ecs-user',
    private_key_file='demo/pic-res-server.pem',
)

# 服务器连接结果
print(lc._init_results)
# 连接成功后, 先获取 SSH Banner
print(lc._shell_cache())

# 执行 pwd 命令并获取输出
print(lc.run('pwd'))
# 执行 ls 命令并获取输出
print(lc.run('ls'))
# 执行 cd db_back/test_database 命令并获取输出
print(lc.run('cd db_back/test_database'))

# 上传文件
upload_result, upload_describe = lc.upload(
    local_path='demo/test_upload.txt',
    target_path='/home/ecs-user/db_back/test_database/test_upload.txt',
)
print(upload_result, upload_describe)

# 下载文件
download_result, download_describe = lc.download(
    target_path='/home/ecs-user/db_back/test_database/test_upload.txt',
    local_path='demo/test_upload.txt',
)
print(download_result, download_describe)

最后看下示例代码的控制台打印效果:

$ (True, '初始化成功')
$ Welcome to Ubuntu 20.04.5 LTS (GNU/Linux 5.4.0-125-generic x86_64)
$
$  * Documentation:  https://help.ubuntu.com
$  * Management:     https://landscape.canonical.com
$  * Support:        https://ubuntu.com/advantage
$ New release '22.04.2 LTS' available.
$ Run 'do-release-upgrade' to upgrade to it.
$
$
$ Welcome to Alibaba Cloud Elastic Compute Service !
$
$ Last login: Wed Mar  1 16:46:42 2023 from 123.45.67.89
$ ecs-user@pic-res-server:~$
$ pwd
$ /home/ecs-user
$ ecs-user@pic-res-server:~$
$ ls
$ db_back  wf-api
$ ecs-user@pic-res-server:~$
$ cd db_back/test_database
$ ecs-user@pic-res-server:~/db_back/test_database$
$ True 本地文件上传成功
$ True 远程文件下载成功
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

拿来即用的 Python SSH+SFTP 实现类 的相关文章

  • Python 如果 kwargs 中的 key 并且 key 为 true

    if force in kwargs and kwargs force is True 感觉应该有更好的方法来编写这个条件 因为我重复了键和变量 假设您确实想检查返回的关键字参数是否is True 这是另一种稍微不同的方式 if kwarg
  • python类型中的__flags__有什么用

    我最近阅读了pickle源代码 以下代码在copy reg让我很困惑 HEAPTYPE 1 lt lt 9 def reduce ex self proto assert proto lt 2 for base in self class
  • 为什么最新的 Python 3.8.x 版本不提供 Windows 安装程序?

    我需要在Windows计算机上安装Python 3 8并希望使用最新的小版本3 8 12 https www python org downloads release python 3812 官方发布网页提供了源代码的 tarball 文件
  • 如何在Python中检查UDF函数中pyspark数据帧列的单元格值为none或NaN以实现前向填充?

    我基本上是在尝试进行前向填充插补 下面是代码 df spark createDataFrame 1 1 None 1 2 5 1 3 None 1 4 None 1 5 10 1 6 None session timestamp id PR
  • tkinter 上的“NoneType”对象没有属性“get”错误[重复]

    这个问题在这里已经有答案了 我最近开始使用 python 3 6 进行编码tkinter并尝试创建我自己的项目repl it 该项目是一个简单的交互式待办事项列表 但是我陷入困境并且无法使该功能正常工作 该函数只是简单地获取条目并将其添加到
  • Pyjnius导入jar文件

    Pyjnius 允许您为 java 类创建 python 包装器 例如 Hardware autoclass org myapp Hardware 有没有办法像这样导入现有的 jar 文件 语法是什么样的 您可以将 jar 添加到 CLAS
  • 如何逐行替换(更新)文件中的文本

    我试图通过读取每一行 测试它 然后写入是否需要更新来替换文本文件中的文本 我不想保存为新文件 因为我的脚本已经先备份文件并对备份进行操作 这是我到目前为止所拥有的 我从 os walk 获取路径 并且保证 pathmatch var 正确返
  • 简单 CAE 的问题

    看起来简单的 CAE 不适用于 Carvana 数据集 我正在尝试对 Carvana 数据集进行简单的 CAE 你可以下载它here https www kaggle com c carvana image masking challeng
  • 如果工作表不存在,Pandas 将工作表附加到工作簿,否则覆盖工作表

    我正在使用 pandas 更新现有的 Excel 工作簿 当使用ExcelWriter对象 我可以覆盖工作表 如果存在 否则创建一个新工作表吗 我的代码附加了新工作表 但是当我尝试覆盖现有工作表时 它会附加一个名称略有不同的新工作表 例如
  • 如何在Python中重命名virtualenv?

    我拼错了名字virtualenv使用以下方法初始化它 virtualenv vnev 我实际上打算创建一个名为的环境venv 尝试重命名后vnev文件夹到venv 我发现这并没有提供太多帮助 激活环境的名称仍然重命名旧的vnev mv vn
  • Odoo:如何覆盖原始功能

    在 Odoo 中 每次打开产品表单时都会计算产品的数量 这发生在模型中product product gt function product available 该函数返回一个名为 res 的字典 Example res 8 qty ava
  • 从 Cython 代码生成 SIMD 指令

    我需要概述在高性能数字代码中使用 Cython 可以获得的性能 我感兴趣的事情之一是找出优化的 C 编译器是否可以对 Cython 生成的代码进行矢量化 所以我决定写下面的小例子 import numpy as np cimport num
  • 在 CSV 文件的最上面一行写入

    我有这个sample csv 文件 a 1 apple b 2 banana c 3 cranberry d 4 durian e 5 eggplant 并有以下代码 samplefile open sample csv rb rows s
  • Python 柯里化任意数量的变量

    我正在尝试使用柯里化在 Python 中进行简单的函数添加 我找到了这个咖喱装饰器here https gist github com JulienPalard 021f1c7332507d6a494b def curry func def
  • 无法从源 pylance 解析导入烧瓶

    我正在学习 Python 课程的一部分是使用 Flask 设置网络服务器 我按照 Flask 安装文档执行了步骤 由于某种原因 flask 模块带有下划线 如下所示 当我将鼠标悬停时 我会得到如下附加信息 无法从源 pylance 解析导入
  • Python,质数检查器[重复]

    这个问题在这里已经有答案了 你好 我正在创建一个函数来检查一个数字是否是素数 但它告诉我 9 是一个素数 def eprimo num if num lt 2 return False if num 2 return True else f
  • 编写 CherryPy 装饰器以进行授权

    我有一个cherrypy应用程序 在某些视图上我想开始只允许某些用户查看它们 并将其他任何人发送到需要授权的页面 有没有办法使用自定义装饰器来做到这一点 我认为这将是最优雅的选择 这是我想做的一个基本示例 class MyApp autho
  • centos上无法安装Pillow

    我上面有 centos 6 3 和 python 2 6 当我尝试通过 easy install 安装它时 出现以下错误 imaging c 76 20 error Python h No such file or directory In
  • Django 表单中的只读字段

    如何在 Django 表单中将字段设置为只读 我知道如何禁用某个字段 但这不是我想要的 任何帮助 将不胜感激 您可以使用可选的attrs定义时的参数Field 以机智 somefield forms CharField widget for
  • Pandas 将时间序列数据重新采样为 15 分钟和 45 分钟 - 使用多索引或列

    我有一些时间序列数据作为 Pandas 数据框 它从每小时过去 15 分钟和过去 45 分钟 时间间隔为 30 分钟 的观察开始 然后将频率更改为每分钟 我想对数据进行重新采样 以便整个数据帧的频率为每 30 分钟一次 15 点和 45 点

随机推荐

  • System用法详细解析

    System类代表系统 系统级的很多属性和控制方法都放置在该类的内部 该类位于java lang包 由于该类的构造方法是private的 所以无法创建该类的对象 也就是无法实例化该类 其内部的成员变量和成员方法都是static的 标准输入输
  • OpenStack实例控制台报错

    OpenStack实例控制台报错1006 环境说明 操作系统信息 CentOS Linux release 7 9 2009 Core Linux node2 yun com 5 4 226 1 el7 elrepo x86 64 Open
  • Mysql数据库第十三课-----------sql语句的拔高3--------直冲云霄

    作者前言 作者介绍 作者id 老秦包你会 简单介绍 喜欢学习C语言和python等编程语言 是一位爱分享的博主 有兴趣的小可爱可以来互讨 个人主页 小小页面 gitee页面 秦大大 一个爱分享的小博主 欢迎小可爱们前来借鉴 sql语句的拔高
  • linux常用性能观测工具---硬件相关

    rdmsr 在Linux内核源码中提供了读写CPU MSR寄存器模块 使可以在用户空间直接读写MSR寄存器 开源社区提供msr寄存器读写工具msrtools 其中有两个命令 rdmsr wrmsr 要使rdmsr wrmsr命令真正可以读写
  • 红帽linux系统安装指导

    第1步 在虚拟机管理界面中单击 开启此虚拟机 按钮后数秒就看到RHEL 7系统安装界面 如图1 26所示 在界面中 Test this media install Red Hat Enterprise Linux 7 0和Troublesh
  • 第九届蓝桥杯单片机省赛题彩灯

    参考佬中佬 但不搬运 http t csdn cn 4tnjz include
  • @responsebody

    表示该方法的返回结果直接写入HTTP response body中 一般在异步获取数据时使用 在使用 RequestMapping后 返回值通常解析为跳转路径 加上 responsebody后返回结果不会被解析为跳转路径 而是直接写入HTT
  • Qt设置部件透明及阴影效果

    窗体和部件透明 设置整个窗体透明属性 0 1 0 完全透明 1 不透明 setWindowOpacity 0 5 窗口透明 部件不透明 设置窗口透明 部件不透明 须配合无边框窗体使用 setWindowFlags Qt FramelessW
  • 教你怎么在linux上永久修改IP地址

    经常移动电脑而使电脑ip改变 有两种方式 在创建虚拟机时使用NAT模式和桥接模式 使用NAT模式 就是虚拟机ip所依赖主机的ip而产生 使用桥接模式 不依赖主机ip而产生ip 相当于在当前网络中有一个电脑占据了这个ip 使用NAT模式 本身
  • HttpMediaTypeNotAcceptableException的解决过程

    今儿的Web项目中突然报错 HttpMediaTypeNotAcceptableException Could not find acceptable representation 涉及接口是 RequestMapping value X
  • JAVA语言特点

    Java语言特点 Java语言是面向对象的 oop Java语言是健壮的 Java的强类型机制 异常处理 垃圾的自动收集等是Java程序健壮性的重要保障 Java语言是跨平台性的 即一个编译好的 class文件可以在多个系统下运行 借助各系
  • 创建Vue3.0工程和常用 Composition API

    一 创建Vue3 0工程 1 使用 vue cli 创建 官方文档 https cli vuejs org zh guide creating a project html vue create 查看 vue cli版本 确保 vue cl
  • QWidget关闭子窗口后显示主窗口(父窗口)

    已经弃坑 不再使用Qt工具 有疑问可以查阅博主的其它博客 看看有没有解决方案 如果还没有可以百度或Google搜一下其他博主的教程 谢谢 Holle 好久没见了 据我上次发博客已经过去很久 今天呢 带给大家一个Qt上的小问题 好多初学者在Q
  • 回顾篇-SpringBoot-Tomcat

    为什么写 今天看了Spring实战第五版 里边有句话如下 传统的基于Servlet的Web框架 如Spring MVC 在本质上都是阻塞和多线程的 每个连接都会使用一个线程 在请求处理的时候 会在线程池中拉取一个工作者 worker 线程来
  • Linux下修改密码命令及查看密码修改时间

    Linux下修改密码命令及查看密码修改时间 1 如修改用户liu的密码为123456 在root用户下执行 echo 123456 passwd stdin liu 2 查看密码修改时间 date d 1970 01 01 more etc
  • CloudCompare——计算点云的KD树并可视化

    目录 1 功能概述 2 完整操作 3 算法源码 4 相关代码 1 功能概述 使用Tools gt Sand box research gt compute kd tree访问此工具 该功能可以用于论文写作中的KD树绘图 2 完整操作 3 算
  • 你的小程序

    你的小程序还未设置管理员信息 无法被绑定 你可先访问mp weixin qq com 在 用户身份 页面设置管理员信息后即可进行绑定 解决方法 换一个浏览器 别用火狐 真XXX 但解决放哪就这么简单
  • 如何在Visual Studio给多行代码进行注释和取消注释

    如何在Visual Studio给多行代码进行注释和取消注释 用鼠标选中要注释的代码 先按Ctr K 键 再按 Ctrl C 键进行注释多行代码 如果要取消注释 用鼠标选中被注释的代码 先按Ctrl K 再按 Ctrl U 即可取消注释
  • 中文OCR识别

    在闲暇时刻做了一个中文识别能力的工程 工程主要对中文文字进行识别 当前工程没有检测文本能力 后续会加入 文本字段在32000验证集合上准确率为83 2 可识别中文字符5990 由于没有时间做太多优化 可能对于相近字体的准确率并不太高 但是可
  • 拿来即用的 Python SSH+SFTP 实现类

    一个拿来即用的 Python SSH 和 SFTP 实现类 可用于 与 Linux 服务器建立持续交互的 SSH 会话 从 Linux 服务器下载远程文件 上传本地文件到 Linux 服务器 新创建一个 linux client py 文件