• python 第三方库paramiko


    介绍

    paramiko是什么可以参考其他人的博客或文章,这里不再赘述,直入正题。

    本次测试的版本信息如下:

    • python 3.9
    • paramiko 2.7.2
    • centos 8

    三种常用方式

    paramiko 的三种常用方式如下:

    • 使用密码进行登录
    • 使用密钥免密码登录
    • SFTP 传输文件

    其中最割裂的就是SFTP 传输文件,很多文章登陆使用SSHClient类,传输文件使用Transport类,我也是这样用了很长时间。
    如果你也是这么用的,你没有啥想法吗?用python就是节约心智,怎么一个变形还能出来两种东西呢,没有办法统一吗?
    网上的统一就是实例化Transport类然后实例化SSHClient类,再把实例化的Transport类添加到实例化SSHClient类。总是有一种别扭的感觉。
    重点:查看源码可以发现,SSHClient类直接提供了 SFTP 传输文件的实例化方法,直接用就行了,世界顿时清爽了很多

    使用密码进行登录

    import paramiko
    
    hostname = 'localhost'
    port = 22
    username = 'aaron'
    # 看密码就知道我是用的redhat系linux系统
    password = 'redhat'
    
    # 实例化SSHClient类
    ssh = paramiko.SSHClient()
    # 远程主机没有本地密钥时的处理规则,主要有三个
    # AutoAddPolicy:直接建立连接,不进行yes/no的确认
    # WarningPolicy:直接建立连接,但是会提示是新连接
    # RejectPolicy:拒绝未知的连接,依赖系统密钥的信息。默认选项。
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    # 连接到服务器
    ssh.connect(hostname, port, username, password)
    
    # 执行命令,获取标准输入、标准输出、标准错误输出,均为流式输入输出
    # 函数原型为 exec_command(self, command, bufsize=-1, timeout=None, get_pty=False, environment=None, )
    # 理论上可以通过标准输入,也就是下面的额stdin变量完成连续输入
    # 同时参数中有布尔型参数 get_pty 可以指定是否获取 tty 通道,这样阻塞输入,比如sudo输入密码什么的都能做。貌似就可以做成你想要的任何东西。
    # 但是以上两点没有验证,貌似比较麻烦,我太懒了-_-|||
    #
    # 另外,exec_command方法每次都是新开一个通道执行命令,执行完成后状态消失。SSHClient类还提供一个invoke_shell方法,这个方法可以连续输入命令。
    # 这两个的区别主要是 invoke_shell使用SSH shell通道,而exec_command使用SSH exec通道。
    # shell通道就是常用的终端软件登陆的通道,登陆变量都会进行加载比如 ~/bashrc 等
    # 而 exec通道 则不进行加载登陆文件,相当于linux桌面系统上右键开terminal一样。
    # 如果你还是不懂,没关系,invoke_shell nb就完事了
    stdin, stdout, stderr = ssh.exec_command('df')
    # 打印输出
    print(stdout.read().decode())
    # 不要忘记关闭连接
    ssh.close()
    

    使用密钥免密码登录

    这里使用密钥文件,但是为了一般情况,我给密钥文件设置了密码,如果你只是想免密码,不设置密码即可.

    在客户机上生成密钥对,将公钥传递给服务器

    ssh-keygen -t rsa # 这里设置密码为redhat_rsa,这里是给密钥设置密码,如果想免密,不设置密码即可
    ssh-copy-id -i ~/.ssh/id_rsa.pub aaron@localhost
    
    import paramiko
    
    hostname = 'localhost'
    port = 22
    username = 'aaron'
    # 这里是密钥文件的密码
    password = 'redhat_rsa'
    # 密钥文件的位置,可以是列表,paramiko会把列表里文件顺序尝试,登陆上位置
    private_key_path = '/home/aaron/.ssh/id_rsa'
    
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    # 这里网上很多先设置pkey传入,但是直接传文件路径也可以,还简单。
    # 我使用的和网上不同,另一个版本请自行搜索,资料n多
    # 如果没有密钥,则不需添加password
    # look_for_keys默认为True,就是会找你 .ssh 目录下有没有合适的密钥文件
    # 也就是说如果密钥文件存在,但是你传 key_filename 时传错了,不影响,paramiko已经替你想好了,这才是正经 python 应有的待遇,舒服!
    ssh.connect(hostname, port, username=username, password=password, key_filename=private_key_path, look_for_keys=False)
    
    stdin, stdout, stderr = ssh.exec_command('ip a')
    print(stdout.read().decode())
    ssh.close()
    

    SFTP 传输文件

    import paramiko
    
    hostname = 'localhost'
    port = 22
    username = 'aaron'
    password = 'redhat'
    
    # 还是SSHClient登陆,以上两种方式都可以。
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(hostname, port, username, password)
    
    # 重头戏,直接使用打开方法即可
    sftp = ssh.open_sftp()
    
    # do something
    # 从这里到下面的ssh.close()为止都是sftp能做的事情,具体能做啥,请看下一个代码段,这里只列举上传(put) 下载(get) 文件,这两个也比较重要
    
    # 回调函数,没想到吧,上传下载还能有回调函数
    # 参数一定,都是传入的两个size,int型数据
    # size 已传输文件累计大小
    # file_size 文件总大小
    def callback(size, file_size):
        print(f"目前传输文件比例: {size} / {file_size}")
    
    # 上传文件,参数都给你们了,看看啥意思就行了
    # 主要就是这个confirm, 如果定义会检测一下上传到服务器文件大小和本地大小是否一致,默认False
    stat = sftp.put(localpath='/tmp/s.avi', remotepath='/tmp/a.avi', callback=callback, confirm=True)
    print(stat)
    
    # 下载文件,同样参数都给你们了,看看啥意思就行了
    sftp.get(localpath='/tmp/s.avi', remotepath='/tmp/a.avi', callback=callback)
    
    ssh.close()
    

    stfp 能 do 的 something

    # 列出当前路径下有什么文件,默认path="."
    print(sftp.listdir())
    
    # 列出当前路径下文件属性,默认path="."
    attrs = sftp.listdir_attr()
    print(attrs)
    print("="*20)
    # listdir函数就是遍历的每个属性的filename
    print(attrs[0].filename)
    print(attrs[0].longname)
    print(attrs[0].st_atime)
    print(attrs[0].st_mtime)
    print(attrs[0].st_gid)
    print(attrs[0].st_uid)
    print(attrs[0].st_mode)
    
    # 就是 listdir_attr 的迭代器版本
    attrs = sftp.listdir_iter()
    print("="*20)
    for i in attrs:
        print(i.filename)
        print(i.longname)
        print(i.st_atime)
        print(i.st_mtime)
        print(i.st_gid)
        print(i.st_uid)
        print(i.st_mode)
    
    # 和内置open用法基本相同,只不过是打开外部文件
    with sftp.open("a.txt", "w") as f:
        f.write("aaa")
    
    # 删除文件,只能删除文件,删除目录使用rmdir函数。文件不存在则报错。
    sftp.remove("/home/aaron/a.txt")
    
    # 文件改名,类似于move
    sftp.rename("/home/aaron/as.txt", "/tmp/soon.txt",)
    
    # 符合posix标准的改名,没有测试
    sftp.posix_rename("/home/aaron/as.txt", "/tmp/soon.txt",)
    
    # 新建目录
    sftp.mkdir("/home/aaron/as")
    
    # 删除目录,类似rmdir,删除的必须为空目录
    sftp.rmdir("/home/aaron/as")
    
    # 返回单个文件的attr信息,如果是软连接则直接返回真实文件信息
    stat = sftp.stat("/tmp/soon.txt")
    print(stat)
    
    # 测试和stat差不多,如果是软链接则返回软链接文件信息
    stat = sftp.lstat("/tmp/soon.txt")
    print(stat)
    
    # 修改权限,权限为八进制数,需要把权限换算为十进制数。比如下面的例子就是权限333
    sftp.chmod("/tmp/soon.txt", 219)
    
    # 修改属主和属组,属主和属组为gid和uid表示。需要有权限。
    sftp.chown("/tmp/soon.txt", 0, 0)
    
    # 设置atime和mtime,如果传入None,则设置为当前时间。否则必须传入两个元素的元组或数组,分别为 (atime, mtime)
    sftp.utime("/tmp/soon.txt", None)
    import time
    sftp.utime("/tmp/soon.txt", (time.time(), time.time()))
    
    # 读取软链接指定的目标
    print(sftp.readlink("/etc/rc.local"))
    
    # 读取软连接制定目标的绝对路径
    print(sftp.normalize("/etc/rc.local"))
    
    # 切换工作路径。SFTP没有工作路径的概念,但是paramiko进行了模拟。如果设置了路径,所有的相对路径都是根据这个路径来的。如果想要切换回去传入None即可。
    sftp.chdir("/tmp")
    
    # 获取当前的工作路径。如果没有使用chdir切换过,则会返回None
    print(sftp.getcwd())
    

    terminal demo

    自己一直想做一个类似xshell的东西,尤其是mac本的iterm或者iterm2是啥垃圾,还被吹的不行不行的,是没用过好东西吗。
    但是每次执行exec_command都会从家目录开始,无法切换目录,十分不方便。一直没有啥进展,知道遇到了 invoke_shell ,一切看起来都有了些可能。

    import time
    from threading import Thread
    
    import paramiko
    
    
    # 接收消息并打印的函数
    # 返回的消息会分成好几段,如果只是发送命令后直接打印是打印不全的,这里直接循环检测缓冲区,有结果就打印。
    def recv_and_print(channel):
        # 定义全局变量,recv_func_flag 此接收函数退出标志,cmd 当前执行命令
        global recv_func_flag, cmd
        while recv_func_flag:
            # 如果此次命令是exit并且退出完成,则设置退出标志
            if cmd == "exit" and channel.exit_status_ready():
                # 打印退出状态,为int型数字
                print(f"此次退出状态:{channel.recv_exit_status()}")
                # 退出标志置为假
                recv_func_flag = False
            # 吐过缓冲区有数据
            if channel.recv_ready():
                # 接收数据
                response = channel.recv(1024).decode().strip()
                # 需要注意的是接收的数据会把传入的命令也返回一遍,这里我们只保留自己打在屏幕上的,不要传回的,所以传回的数据如果和命令相同则不打印,略过
                if response != cmd:
                    print(response, end="")
    
        print("接收函数退出......")
    
    
    # 定义全局变量
    recv_func_flag = True
    cmd = ""
    
    # ssh登陆,老一套东西
    hostname = 'localhost'
    port = 22
    username = 'aaron'
    password = 'redhat010;'
    
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(hostname, port, username, password)
    
    # 获取invoke_shell
    invoke_shell = ssh.invoke_shell()
    # 接收函数使用另一个线程运行,因为和当前主线程一同退出,所以不需要join方法
    t = Thread(target=recv_and_print, args=(invoke_shell,))
    t.start()
    
    # 主线程退出标志
    flag = True
    while flag:
        # 输入命令
        cmd = input()
        # 输入命令必须有回车才会执行,这里我发送的是linux命令,
    之后能执行命令,如果系统不同,需要测试
    等回车字符
        invoke_shell.send(f"{cmd}
    ")
        # 如果命令是exit则退出循环
        if cmd == "exit":
            flag = False
    
    # 检测接收函数已经退出,这里停止0.5s是因为退出命令发送给服务器,服务器会返回注销的信息,之后检测接收函数才会完全退出,认为设置了一个等待时间,这个时间因为是本机,设置的相对不长,如果是其他主机,需要根据网络以及超时情况进行设置
    while recv_func_flag:
        time.sleep(0.5)
    
    # 别忘了关闭ssh
    ssh.close()
    

    这个demo直接运行然后输入命令即可,就像是使用terminal直接登陆一样。
    ll等定义的alias也是能使用的。
    但是双击tab ctrl+c 等没有实现,留待诸君完善吧

    这个demo目前还有一些问题。时间原因也懒得解决了,以后用到的时候再深入探究吧。

    • 换行总是有问题,时好时坏,感觉每次发送数据有时命令、结果、信息提示符有时合并发送,有时分开发送,没有啥必然规律。也许和linux发行版有关?和tcp通信有关?目前原因不明(具体现象请自行测试)
    • 显示信息使用了 print(response, end="") 退出时也使用相同的显示命令,到时系统注销的显示信息和函数的提示信息"接收函数退出......"拼接在一起了(对啊,提示信息之前我可以加个回车啊,哎呀,不再测试了,太累了)
    • su命令也可以正常执行,输入密码啥的不影响,但是密码会明文显示。。。。。
    • 接上条,su退出成问题,su退出使用exit,整个函数就退出了。算了,不修复了/-_-

    推广个人网站 RedQueen 网站有漏洞,仅作试看测试使用

  • 相关阅读:
    gojs常用API (中文文档)
    webpack的安装
    win10如何将wps设置成默认应用
    gojs常用API-画布操作
    Access中替代case when的方法 .
    C++ 11 中的右值引用
    形参前的&&啥意思?
    【C语言学习笔记】字符串拼接的3种方法 .
    java项目打jar包
    教你用DrawLayout 实现Android 侧滑菜单
  • 原文地址:https://www.cnblogs.com/cdinc/p/14421311.html
Copyright © 2020-2023  润新知