paramikopython
*paramiko须要PyCrypto模块的支持shell
paramiko支持经过SSH协议进行一些操做,好比远程执行命令,上下传文件等等vim
用法:服务器
① 远程命令session
ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) #指定当对方主机没有本机公钥的状况时应该怎么办,AutoAddPolicy表示自动在对方主机保存下本机的秘钥 ssh.connect('ip',22,'user','passwd') #SSH端口默认22,可改 stdin,stdout,stderr = ssh.exec_command("命令内容") #这三个获得的都是类文件对象 outmsg,errmsg = stdout.read(),stderr.read() #读一次以后,stdout和stderr里就没有内容了,因此必定要用变量把它们带的信息给保存下来,不然read一次以后就没有了 if errmsg == "": print outmsg ssh.close()
② 文件交流ssh
tra = paramiko.Transport(('ip',22)) #参数是一个tuple tra.connect(username='...',password='...') #必定要指明参数名的username和password。不然会报错str has no attribute 'get_name' sftp = paramiko.SFTPClient.from_transport(tra) sftp.put('本地路径','远程路径') #上传文件 sftp.get('远程路径','本地路径') #下载文件 tra.close()
须要注意的是在put和get方法中,两个路径都是须要完整的(要带文件名!)异步
sftp的put和get方法还有callback这个参数。这个参数指定一个函数对象,这个函数应该接受两个int型参数,分别表明了已上传/已下载的字节数;总的要上传/下载的字节数。利用callback的指定能够作出一个相似进度条的功能。另外,还须要注意的是callback会在传输完成以前不断地被调用可是具体是怎么样的时机下调用我不是很清楚,须要研究下paramiko的源码。可是能够肯定的一点是这个callback函数也是在主线程中调用的,因此最好不要在里面写什么sleep,这样会致使整个传输过程变慢的。函数
■ Transport的更多扩展spa
今天想经过python作一个SSH模拟终端。想法很是简单,就是经过SSHClient类创建链接而后进行命令和返回的交互嘛。不过发生了不少问题,去网上一找才发现,原来SSHClient类的exec_command方法是个单session包装的方法。即调用这个方法只能执行一趟命令,执行完成以后就断开了链接,再次执行时又是新的session。好比:命令行
ssh.exec_command('cd /tmp;pwd')的返回是/tmp,但若是把cd和pwd分红两个exec_command写的话,pwd最终返回的是HOME目录,这代表了exec_command的单会话特性。那么怎么样才能从更底层开始创建命令交互的通讯? 网上小查了一种方法仍是须要Transport这个咱们以前在SFTP时候用的类。
作法:
tran = paramiko.Transport(sock=(ip,22)) tran.connect(username='xxx',password='xxx') channel = tran.open_session() channel.get_pty() channel.invoke_shell() channel.send('ls\n') result = '' while True: time.sleep(0.5) res = channel.recv(65535).decode('utf8') result += res if result: sys.stdout.write(result.strip('\n')) if res.endswith('# ') or res.endswith('$ '): break
经过这样的方式搭建出来的一个SSH命令通道是和Xshell这种软件创建出来的终端差很少的,好比有终端命令行提示符,也支持cd等命令。
在获取命令运行的返回(recv方法的返回)时,咱们用了一个while True的逐次取数据的方式。这么作的一个好处就是当返回比较多比较大的时候能够顺利读取彻底。其实这么写也是有其必要性在里面的。若是直接在这个代码的循环外面直接recv一下,返回获得的会是'\r\n'而不是ls返回的文件信息。什么原理不清楚可是既然while True这个方式有必要性又有优势的话就能够考虑用下。
跳出循环的方式是判断返回的结尾是否是终端命令提示符的结尾#+"空格"或者$+"空格",这种判断方法比较不健壮。网上也有用正则匹配或者其余的一些方法来识别返回结果读取到头了,可参考。这种方法创建的SSH通道的话,是自带命令行提示符的,并且每条命令的返回实质上是“真的返回”+"\n"+"命令提示符",因此能够作到每个命令返回以后后面就有命令提示符。
另外一方面,这个通道也并非很万能的,好比对经过stdin进行交互要怎么作目前我尚未找到办法、所以也就也意味着对vim,crontab -e之类的对交互有需求的软件就不是支持很好了。
■ 更多扩展
● 在connect方法里还有参数timeout = float设置链接超时时间
在connect方法中,还可用参数pkey指定本机私钥文件用于身份验证,内容能够是个PKey类对象。也能够用key_filename = '路径' 这个参数来指定一个文件。而在SSHClient类还有方法load_system_host_keys用于指定对方主机存放本机公钥的位置,默认不加参数的话是将这个位置设置为~/.ssh/known_hosts。SSHClient类对象connect以前,先用load_system_host_keys指定本机公钥存放位置,再在connect的时候指明本机私钥的话,就不用password,只要username就能够登陆了。
● 关于set_missing_host_key_policy
这个方法的参数有三种选择,分别对应三种当对方主机没有在相关文件中找到本机的公钥时作的动做:
paramiko.AutoAddPolicy() 自动添加本机的公钥和主机名进相关文件
paramiko.RejectPolicy() 自动拒绝未知的主机名和密钥,“未知”指的是没有在相关文件中有记录的主机
paramiko.WarningPolicy() 和AutoAdd没差,只不过在出现没有记录的状况时警告一下
● SFTPClient类除了上面提到的那些方法之外,还有:
sftp.mkdir('路径',mode) #mode不用加引号,直接写755之类的便可,方法直接建立目录并按mode设置其权限
sftp.remove('路径') #删除某目录
sftp.stat("文件路径") #获取文件信息
sftp.listdir("路径") #以列表方式返回目录下的内容
以上这些方法和exec_command执行一些特定的命令是差很少的,实践的时候也没必要一棵树吊死在exec_command上,也能够适当考虑这些方法。
● ssh.exec_command的异步原理
据我估计ssh.exec_command应该是单独开一条线程来远程执行命令,而当咱们对命令的输出不感兴趣,在exec_command执行以后不调用read来收集远程返回的信息的话,那么这个线程是不阻塞的。这是个小坑的地方。好比下面两个例子,ssh是一个已经配置好的SSHClient对象:
stdin,stdout,stderr = ssh.exec_command("sleep 5s;echo foo") stdin,stdout,stderr = ssh.exec_command("echo bar") out,err = stdout.read(),stderr.read() if err: print err else: print out #这种状况下的输出只有bar
######另外一种状况###### stdin,stdout,stderr = ssh.exec_command("sleep 5s;echo foo") out,err = stdout.read(),stderr.read() stdin,stdout,stderr = ssh.exec_command("echo bar") out,err = stdout.read(),stderr.read() if err: print err else: print out #这种状况下先等5秒而后输出foo和bar
能够看到第一种状况中,没有读取stdout等的信息,那么本地程序直接往下执行,无论以前的命令是否还未给出结果,而第二种状况,因为我要读取内容,因此必须等到上一条命令执行完成给出结果本地才继续跑下一条命令。
● stdin的用法
以前因为基本上都是从服务器上面读数据,对于stdin这个变量一直感受很鸡肋。直到那天要批量改服务器密码。。试了一下果真stdin是能够拿来进行write的。记得在write的时候要适当插入回车\n来模拟敲回车的过程,不然极可能会出现SSH被堵塞的状况。好比改密码这个过程:
##前面配置过程省略## stdin,stdout,stderr = ssh.exec_command("passwd") stdin.write("old_password\nnew_password\nnew_password\n") #由于密码要确认,因此要输两遍 out,err = stdout.read(),stderr.read() if err != '': print err else: print out