在使用Python编写一些脚本的时候,在某些情况下,我们需要频繁的登录远程服务执行命令并返回一些结果。在shell环境下,我们是这样操作的。$sshpass-p${passwd}ssh-p${port}-l${user}-oStrictHostKeyChecking=noxx.xx.xx.xx"ls-l"然后你会发现你的输出有很多不需要,但有些信息无法去除(或许有办法,请留言交流),类似这个主机:xx.xx.xx.xx,端口:xxWarning:Permanentlyadded'[xx.xx.xx.xx]:xx'(RSA)到已知主机列表。登录失败:[Errno1]此服务器未注册到rmp平台,请确认是否cdnserver.total4-rw-r--r--1rootroot239Mar302018admin-openrc对于直接使用shell命令执行命令的,可以直接使用管道,或者将标准输出重定向到文件,获取执行命令返回的结果1.如果使用subprocess使用Python来做这件事,通常我们会第一时间想到使用一些命令执行库如os.popen、os.system、commands、subprocess来间接获取。但据我所知,这些库得到的输出不仅是标准输出,还有标准错误(也就是上面的冗余信息),所以每次输出需要清洗和格式化时,我们可以得到我们想要什么。你想要的数据。以subprocess为例,像这样importsubprocessssh_cmd="sshpass-p${passwd}ssh-p22-lroot-oStrictHostKeyChecking=noxx.xx.xx.xx'ls-l'"status,output=subprocess...如果避不开密)痛点二:干扰信息太多,数据清洗格式化相当麻烦痛点三:代码实现不够优雅(有点脏),可读性太差痛点4:SSH连接不能复用,只能执行一个连接痛点5:代码不能在所有平台上使用,只能在Linux和OSX上使用。为了解决这些问题,我在全网搜索了PythonSSH相关的文章,果然找到了两个库sh.sshParamiko2。使用sh.ssh先介绍第一个。sh.sshsh是一个库,可以让你通过函数调用来完成Linxu/OSX系统命令。它非常好用,有机会我会写一篇关于它的介绍。$python3-mpipinstallsh今天只介绍它的一个功能:ssh通常是在两台机器之间进行通信。为了方便起见,您可以设置免密登录,这样就不需要输入密码了。这段代码可以实现免密码登录,执行我们的命令ls-lfromshimportsshoutput=ssh("root@xx.xx.xx.xx","-p22","ls-l")print(output)但是有可能我们不想设置没有密码的互信。为了让这段代码更通用,我假设我们没有设置passwordfree,只能使用密码登录,问题就来了。要输入密码,您必须使用交互式方法输入。如何在Python中实现它?原来ssh方法接收一个_out参数,可以是代表文件路径的字符串,也可以是文件对象(或类文件对象),也可以是回调函数,表示当有标准输出时,会该调用将输出传递给此函数。这很容易处理。只要我能识别password:这个词,只需将我的密码写入标准输入即可。完整代码如下:importsysfromshimportsshaggregated=""defssh_interact(char,stdin):globalaggregatedsys.stdout.write(char.encode())sys.stdout.flush()aggregated+=charifaggregated.endswith("密码:"):stdin.put("you_password\n")output=ssh("root@xx.xx.xx.xx","-p22","ls-l",_tty_in=True,_out_bufsize=0,_out=ssh_interact)print(output)这是官方文档给出的一些信息,写的一个demo。尝试运行后发现程序会一直运行,永远不会返回,永远不会退出,回调函数永远不会进入。通过debug查看源码,还是没找到问题所在,于是上Github搜索。原来这个问题在2017年就已经存在了,一直到2020年才修复,好像用sh.ssh的人不多,于是又“跟进”了,希望能得到回复。只有在需要输入密码时才会出现上述问题。如果你设置了机器之间的互信,就没有问题。为了体验使用sh.ssh的效果,我设置了无密码机器互信,然后使用如下代码。fromshimportsshmy_server=ssh.bake("root@xx.xx.xx.xx","-p22")#相当于执行一次登录,执行一次命令,执行完退出登录print(my_server.ls())#sleep时可以手动登录服务器,使用top查看当前连接了多少终端time.sleep(5)#再次执行该命令时,登录终端数会是+1,执行完后是-1但实际上,你可以在远程机器上,执行top命令,查看连接终端的变化,会先+1,再-1,说明这两个命令的执行是通过两个连接实现的。从这点来看,使用sh.ssh可以解决痛点1(如果能解决以上问题)、痛点2、痛点3。但是还是不能复用ssh连接,还是不方便,不是我理想中的最佳解决方案。最重要的一点是sh模块只支持Linxu/OSX,你必须使用它的兄弟库——Windows上的pbs。然后又去pypi看了一下pbs,已经“久久失修”,没人维护了。至此,我离“棋子”还差最后一根稻草。3、使用paramiko抱着最后的希望,尝试使用paramiko库,终于在paramiko中找到了本该属于Python的优雅。可以通过以下命令安装$python3-mpipinstallparamiko然后,接下来介绍几种常用的ssh登录方法方法一:根据用户名和密码进行sshclient登录然后可以参考下面的代码,在RemoteLinux/OSX系统下的连接importparamikossh=paramiko.SSHClient()#允许连接到known_hosts文件中没有的主机ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())#建立连接ssh.connect("xx.xx.xx.xx",username="root",port=22,password="you_password")#使用此连接执行命令ssh_stdin,ssh_stdout,ssh_stderr=ssh.exec_command("ls-l")#获取输出print(ssh_stdout.read())#关闭连接ssh.close()方法二:基于用户名和密码的传输方式登录方法方法一是传统的连接服务器,执行命令,关闭的操作。多个操作需要多次连接,连接无法复用【痛点四】。有时需要登录服务器进行多项操作,如执行命令、上传/下载文件等。方法一无法实现,可以使用transport方法。importparamiko#建立连接trans=paramiko.Transport(("xx.xx.xx.xx",22))trans.connect(username="root",password="you_passwd")#指定sshclient对象的传输如上transssh=paramiko.SSHClient()ssh._transport=trans#其余同上ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())ssh_stdin,ssh_stdout,ssh_stderr=ssh.exec_command("ls-l")print(ssh_stdout.read())#关闭连接trans.close()方法三:基于公钥的SSHClient登录keyimportparamiko#指定本地RSA私钥文件#如果创建密钥对时设置了密码,password为设置的密码,如果不需要指定密码参数pkey=paramiko.RSAKey.from_private_key_file('/home/you_username/.ssh/id_rsa',password='12345')#建立连接ssh=paramiko.SSH客户端()ssh。connect(hostname='xx.xx.xx.xx',port=22,username='you_username',pkey=pkey)#executecommandstdin,stdout,stderr=ssh.exec_command('ls-l')#把result到stdout,如果有错误,会放到stderr中私钥文件#如果在创建密钥对时设置了A密码,password为设置的密码,如果没有则需要指定密码参数pkey=paramiko.RSAKey.from_private_key_file('/home/you_username/.ssh/id_rsa',password='12345')#建立连接trans=paramiko.Transport(('xx.xx.xx.xx',22))trans.connect(username='you_username',pkey=pkey)#指定sshclient对象的transport如上transssh=paramiko.SSHClient()ssh._transport=trans#执行命令,与传统方法相同stdin,stdout,stderr=ssh.exec_command('df-hl')print(stdout.read().decode())#关闭连接trans.close()以上四种方法可以帮助你远程登录服务器执行命令。如果需要复用连接:在一个连接中执行多条命令,可以使用方法二和方法四,使用后记得关闭连接,实现sftp文件传输。同时paramiko是ssh的完美解决方案。很专业,也可以用来sftp文件传输。importparamiko#实例化一个trans对象#实例化一个transport对象trans=paramiko.Transport(('xx.xx.xx.xx',22))#建立连接trans.connect(username='you_username',password='you_passwd')#实例化一个sftp对象,指定连接的通道sftp=paramiko.SFTPClient.from_transport(trans)#发送文件sftp.put(localpath='/tmp/11.txt',remotepath='/tmp/22.txt')#下载文件sftp.get(remotepath='/tmp/22.txt',localpath='/tmp/33.txt')trans.close()这里Paramiko赢了,但是还有一个痛点我们没有提到,它是多平台的,我们在谈论Windows,这里有好事也有坏事。好处是:paramiko支持windows。不好的是:需要做很多复杂的准备工作,可以google一下解决,但是建议还是放弃吧,坑太深了。注意事项使用paramiko时,有一点需要注意。这是我“踩坑”后发现的。其实我觉得这个设计挺好的。如果不需要等待它返回数据,可以直接实现异步效果。只是对于不了解这个设计的人来说,确实很容易卡住,就是在执行ssh.exec_command(cmd)的时候,这个命令并没有被同步阻塞。比如执行下面的代码,你会发现脚本立即退出,并没有等待5秒才执行ssh.close()importparamikotrans=paramiko.Transport(("172.20.42.1",57891))trans.connect(username="root",password="youpassword")ssh=paramiko.SSHClient()ssh._transport=transstdin,stdout,stderr=ssh.exec_command("sleep5;echook")ssh.close()但是如果你改成这样,加上一行stdout.read(),paramiko就会知道你需要这次执行的结果,它会阻塞在read()中。导入paramikotrans=paramiko.Transport(("172.20.42.1",57891))trans.connect(username="root",password="youpassword")ssh=paramiko.SSHClient()ssh._transport=transstdin,stdout,stderr=ssh.exec_command("sleep5;echook")#添加一行read()print(stdout.read())ssh.close()4.经过一些比较和一些例子,我可以看出Paramiko是一个专业且省心的ssh工具。个人认为Paramiko模块是运维人员必学的模块之一。如果你正好需要用Python代码实现ssh到远程服务器获取一些信息,那么我把Paramiko推荐给你。最后,希望这篇文章能对你有所帮助。如果觉得文章还不错,欢迎关注公众号:Python编程学习圈,或者去编程学习网了解更多编程技术知识,还有海量干货学习资料!
