firstly,版权没有,随意COPY && PASTE,但注意注明出处即可。
前言,这里讲Expect的只言片语,具体要了解需要熟悉TCL语言的相关知识。这里举了最常用的问题,就系统管理而言,足以满足日常需求,若是你做软件自动化测试。要深入了解expect,请下载http://bbs.chinaunix.net/thread-1769951-1-1.html此外还需要熟悉TCL,毕竟它和TCL相关。 一,expect的FAQ 如何匹配多种情况,典型的例子就是ssh ,第一个可能是yes,然后password,或是直接password。就是并行匹配的情况,见下面例子。
- #!/usr/bin/expect
- set timeout 60
- set pwd “该机器的密码”
- spawn ssh 10.10.10.1
- expect {
- “\[#$\]“ {send “\r” } ### 假如有了ssh 公钥之类的,直接回车。当然普通用户下边还可能需要sudo,自己处理一下吧。
- “not know” {send_user “[exec echo \"not know\"]“;exit}
- “(yes/no)?” {send “yes\r”;exp_continue} #continue的意义,靠猜测也能差不多知道了吧?可以Man expect
- “password:” {send “$pwd\r”}
- “Permission denied, please try again.” {
- send_user “[exec echo \"Error:Password is wrong\"]“
- exit }
- }
复制代码
#### 把所有可能出现的情况列举出来做匹配,假如写法如下就是串行执行了。 expect “*#” send “ifconfig\r” send “exit\r” expect eof # 只有spawn产生的进程的相关信息才能被expect捕捉到,还包含2个特殊情况,eof和timeout,eof关闭spawn 产生的spawn id :exp_id,也就是结束标记。这个eof是必不可少的,至于为什么,我也没找到权威答案,包括手册都没提到,但实践中发现很多时候不加会导致得不到你要的结果。请看下边的一个例子:
- #!/usr/bin/expect -f
- set ip 10.1.1.1
- set pwd 123456
- spawn scp ssh.exp root@$ip:/tmp
- expect {
- “(yes/no)?” {send “yes\r”;exp_continue}
- “password:” {send “$pwd\r”}
- }
- # expect eof
复制代码
假如最后的expect eof给注释了,文件不会被scp到10.1.1.1上,也许你感觉很奇怪,关键就在于这个eof,我看了相关资料包括debug信息没有找到令人信服的答案。 我个人猜测是,没有eof,那么仅仅是把密码send过去,连回车都没有执行。然后就异常退出了。加了eof部分,让Expect执行完毕,下边才能退出子程序。
注意set timeout 这个要保证你下边的command能完成 比如你scp一个文件需要30s,而你set timeout 10那么会超时退出的。
其他自己看tcl和expect的manual吧,假如你想把ip,密码放文本里,也可以,只需要set 参数,具体到我博客看,我懒得贴了。大致是 set name [lindex $argv 0],或看下边的例子。
—————————————————————————— 添加一个mysql,实际上是expect的interact的例子 #!/usr/bin/expect
set pwd 123456 spawn mysql -uroot -p
expect “password” send “$pwd\r” expect “mysql>” send “show databases;\r” interact
这样控制权交给了mysql命令行,你可以手工执行各种操作了。有的时候可能需要这个。 ## for 循环一例 改密码,for循环的sample spawn passwd test for {set i 1} {$i<=2} {incr i} { expect “password:” send “1\#23abc\r” } expect eof =============================================================
注意expect的特殊情况,需要转义,仅仅列举一些例子。 比如expect “#” send “ps -ef|grep cpusd|grep -v $$ |awk ‘{print \”kill -9 \”\$2}’|sh\r” 杀死远程机器上cpusd进程,awk后边的”和$2都要\转义,否则$2是一个变量会报错。“转义是因为开头有了”,会认为extra close-quota. set定义的变量也是如此,比如你set pwd 123\abc ,那么应该写成set pwd 123\\abc来转义。
如何得到命令的结果? 有时候你send一个命令,希望得到结果,并加以判断,需求在这里http://bbs.chinaunix.net/thread-3575650-1-1.html 你可以看下expect_out(buffer)这个buffer保存了上一个命令输出的到匹配处之间的输出,可以通过正则来提取出来。 send “ip addr|awk ‘/eth0/&&/32/{print \$2}’\r” #执行一个shell命令,看结果是否是172.10.0.249/32 expect -re {.*\/(.*)\r\n} { #可以用正则的子串来提取命令执行结果,expect_out(0,string)是整个正则匹配的内容,1是第一个子串匹配的内容,有趣的是,包括你send过去的命令连同shell 提示符号[root@test.com~]# 也被被当做输出。这个可以通过debug看出来,回车符号是\r\n。至于子串匹配,可以看手册,一共有9个,当然第二个就是expect_out(2,string),以此类推。 set ip $expect_out(1,string) 把这个子串正则匹配的结果赋给IP }
if {$ip == “172.18.0.249/32″ } { #判断是否,做动作。 send “who\r” }
如何获取一个本地的shell,在expect里使用? 其实这个在上边已经出现过了,只需要exec即可。比如获取本地的IP地址 set ip [exec shell_command]
如何关闭日志?log_user 0,当然你可以在某个阶段关闭,某个阶段开始,log_user 1开启。所有的输出记录在expect_out(buffer)和send过去的command都被关闭。如何记录日志?log_file 日志名字。具体看手册
如何远程执行本地写好的脚本?需求见。http://bbs.chinaunix.net/viewthr … 2585&from=favorites 利用tcl文件操作知识,把脚本内容读取赋给cmd,然后send过去。 open urscript [ open A.sh ] while {[ gets $urscript cmd ] >= 0} {send “$cmd\r”} close $urscript
具体请后边连接,但不够通用,比如脚本需要带上参数这个办法可能就不太好了 如何把ip,密码写文本里,然后批量执行?请看下边的sample(ssh.exp)
- set f [open ip r]
- while { [gets $f line ]>=0 } {
- set ip [lindex $line 0]
- set pwd [lindex $line 1]
- spawn ssh $ip
- expect {
- “not know” {send_user “[exec echo \"not know\"]“;exit}
- “(yes/no)?” {send “yes\r”;exp_continue}
- “password:” {send “$pwd\r”}
- “Permission denied, please try again.” { send_user “[exec echo \"Error:Password is wrong\"]“
- exit }
- }
- expect “#”
- send “ifconfig\r”
- expect “#”
- send “exit\r”
- expect eof
- }
- close $f
复制代码
其中ip里放的是ip ,密码,只需要执行expect -f ssh.exp 即可。这个open,close完全是tcl的文件操作知识。
二,高级话题,你真的懂expect吗?它是如何工作的?为何有诡异问题出现?为何有时候取不到send过去的命令的执行结果??——有空写!
三,一个批量执行任务的python脚本(IT民工的福音啊)
民工们一般维护同类的机器很多,有的时候需要所有机器都做一个操作,比如20台机器都要安装mysql之类的,最开始的时候咋处理?稍微聪明点的办法,写安装脚本,调试测试OK,一个个登陆上去执行。或是安装好了打个包,然后用脚本替换配置文件等。还要20台一个个登陆,万一机器多到50,100台这种事枯燥而且易出错,真是民工的活啊。在好友的帮助下,经我反复测试写了一个Mssh.py(multipart-job ssh),适用于以上场景。 简单说明:Linux 平台,python >=2.6(多数linux版本包括不限于RHEL5,CENTOS5都是2.4.3,哪怕你yum 升级也是2.4.3,需要额外安装2.6,因为需要多进程的模块,而2.6以后才有这个模块) 另外需要安装pexpect(一个expect-like)的模块,假如yum安装的话,需要拷贝/usr/lib/python2.4/site-packages/pexpect.py到2.6的目录下/usr/local/lib/python2.6/site-packages/,否则会报错,没有这个模块,如果自己下载安装,我没试过,可能也类似需要放到这个目录下。
说明,1.假如机器允许root登录,此时把需要登录的ip password放一个文件hostlist里,格式 ip password 比如10.10.10.1 123abc 可以写注释,以#开头 比如: #nginx host 10.10.10.1 123456 。。。 但是不能有空行,否则会报错。 比如: 10.10.10.1 这里不能有空行 10.10.1.2 —————- 2.禁止root登录,只允许普通用户,假如是test用户登录,然后sudo。 那么hostlist格式 ip test用户的密码 sudo到root的密码 比如: 10.1.1.1 123456 superpwd 同样不能有空行,可以加#注释
更新:删除了之前发的python脚本,更新至最新的多进程测试完毕的版本,考虑到了root登陆,非root然后sudo,要输入yes,passwd,直接passwd,ssh信任(无密码和很BT的有密码的2种),端口非22等各种特殊情况。除了ssh信任key未测外,其他都测试完毕。测试ssh key有一些问题,在改动特别感谢好友jiaion给的帮助
首先在机器上mkdir /tmp/mssh/
为了防止粘贴变形,把脚本内容放在附件里。
测试一下:python mssh.py,会出现提示
- Usage: mssh.py -f hostlist -u user -c “cmd” versrion 1.1
- Options:
- –version show program’s version number and exit
- -h, –help show this help message and exit
- -f FILE, –file=FILE host list ,which stores ip and password
- -u USER, –user=USER username,root or other users
- –port=PORT sshd’s port ,default:22
- -d, –debug Output debug messages
- -c CMD, –cmd=CMD commadn to be exected,don’t forget to type “”, e.g
- “ifconfig”
复制代码
完整的用法,python mssh.py -f hostlist -u root -c “ifconfig” –port 22 -d 建议执行命令要加上“”,比如”ifconfig” 其中–port 22可以不加,但port非22要–port portnum 比如–port 51223 -d 观察整个执行过程的输出,debug用,但建议慎用,因为多进程,信息输出打乱了。 看似只能执行简单的ifconfig,好吧,给你个好办法,把你的脚本调试完毕,放到一个http server上,我一般建议放监控(cacti)上,路由最全,而且肯定网络通,为啥,自己想去。然后执行批量下载命令 python mssh.py -u root -c “wget http://ip/scrpit.sh” 然后执行python mssh.py -u root -c “run scrpit.sh” 多爽啊,几十台机器都做一个事,你需要的仅仅是写好各种脚本即可,就看你的脚本能力了。 假如你熟悉python,完全可以把执行的脚本或命令放到本地,然后read整个内容,赋值给cmd,然后send过去,但这种有很大的弊端,不够通用。 以上对非root也可以,python mssh.py -u test -c 。。。 特别注意: 不要在执行的命令里出现交互的情况,除非你能解决这个交互。比如python mssh.py -u root -c “yum install expect” 这个结果会等待到超时抛出异常,因为有个y/n,让你输入,你要明确指定yum -y install expect 第二,你的执行机器上的环境变量直接决定了脚本是否能执行成功,比如你python mssh.py -u root -c “command”,而这个命令没在$PATH里,那肯定是报错的. 第三看,不要写错ip,password,sudopwd,否则。。。
最后无-d 参数,整个执行日志记录在/tmp/mssh/$ip,以IP为日志名字,是追加的。
为了减轻广大的民工敲键盘的负担,耗费一个多星期反复测试的东西,请随意COPY,只要注明出处即可]。Enjoy it 也希望能替代puppet,cfengine之类的(这种原理大同小异,除了安装维护需费时费力外,还需要熟悉模板的描写,加上ZH_CN中文资料甚少。。。)
#!/usr/local/bin/env python #-*- coding:utf-8 -*- # Author :expert1 # contact atiaofu68#live.cn # get more information ,please visit at :http://bbs.chinaunix.net/thread-3566066-1-1.html # free to copy and use after reserved the above message ! import pexpect,sys,time import signal,datetime import multiprocessing from optparse import OptionParser class Notcmd: pass class Mssh(multiprocessing.Process): def __init__(self,ip,user,passwd,cmd,port,debug): multiprocessing.Process.__init__(self) self.ip=ip self.user=user self.passwd=passwd self.cmd=cmd self.port=port self.debug=debug def run(self): try: if not cmd: raise Notcmd now=time.strftime("%Y-%m-%d %H:%M:%S") print "start time: %s ----- %s " % (now , self.ip) ssh = pexpect.spawn('ssh %s@%s %s' % (self.user, self.ip, self.port),timeout=120) if debug == None: #%(time.strftime("%Y%m%d%H")) #need to import time module mlog='/tmp/mssh/' + self.ip f = open(mlog,"a+") f.write('\n################ %s start at: %s #############\n'%(self.ip,now)) ssh.logfile_read = f #ssh.logfile_send = f else : f=open('/tmp/tmp.log',"a+b") ssh.logfile_read=sys.stdout i = ssh.expect(['(?i)password','continue connecting (yes/no)?','[$#]','No route to host','pexpect.TIMEOUT']) if i == 0: ssh.sendline(self.passwd) elif i == 1: ssh.sendline('yes') ssh.expect('password') ssh.sendline(self.passwd) elif i == 2: ssh.sendline() #pass elif i == 3: print 'couldn\'t connect to host ',ssh.before else : print 'ssh to host timeout ,please check network and pasword ' #ssh.expect('(?i)terminal type\?') #ssh.sendline('vt100') if user == 'root' : pass else : ssh.expect('$') #ssh.logfile_read = None ssh.sendline('sudo su -') i = ssh.expect(['[pP]assword','#']) if i == 0: ssh.sendline(sudopwd) else : ssh.sendline() ssh.expect('#') ssh.sendline(cmd) ssh.expect('#') ssh.sendline() f.write('\n#################### %s task finished ##################\n'%self.ip) except pexpect.EOF: ssh.close() except Notcmd: print "Mssh Error -- %s server xxnot command" % self.ip sys.exit(1) except pexpect.TIMEOUT: ssh.close() print "Mssh Error -- ssh to %s server connect timeout" % self.ip sys.exit(1) except Exception,e : ssh.close() print " connect error,",str(e) sys.exit(1) else: ssh.close() finally: f.close() def Argument(Print="No"): parser = OptionParser(usage="%prog -f hostlist -u user -c \"cmd\" versrion 1.1",version="%prog xiaofu V1.1") parser.add_option("-f", "--file",dest="File",action="store",help="host list ,which stores ip and password") parser.add_option("-u", "--user",action="store", dest="User",help="username,root or other users") parser.add_option("--port",action="store", dest="Port",help="sshd's port ,default:22") parser.add_option("-d", "--debug",action="store_true", dest="debug",help="Output debug messages") parser.add_option("-c", "--cmd",action="store", dest="Cmd", help="commadn to be exected,don't forget to type \"\", e.g \"ifconfig\"\n ") (options, args) = parser.parse_args() ArgvDict = vars(options) if Print == "Yes" : parser.print_help() return ArgvDict def signal_handler(signal, frame): print "Kill All Process" sys.exit(0) def Main(): hostlist=[] start = datetime.datetime.now() ArgvDict = Argument() File = ArgvDict["File"] global user user = ArgvDict["User"] global cmd cmd = ArgvDict["Cmd"] global debug debug = ArgvDict["debug"] global port port = ArgvDict["Port"] signal.signal(signal.SIGINT, signal_handler) if port : port = "-p "+port else : port = "" file = open(File,"r") try: while True: line = file.readline() if len(line) == 0:break i = line.strip() if i[0] == "#" : continue host= i.split() hostlist.append(host) for i in hostlist: ip=i[0] passwd=i[1] if len(i) == 3 : global sudopwd sudopwd=i[2] p=Mssh(ip,user,passwd,cmd,port,debug) x=[] x.append(p) p.start() for t in x: t.join() end = datetime.datetime.now() print '\n---------------------------------------------' print "All tasks finished! time consuming :",end - start,"\n" except Exception,e: print "\nError ,reason is : ",str(e),"\n" finally: file.close() if __name__ == "__main__": if len(sys.argv[1:]) >= 3: Main() else: Argument(Print="Yes") sys.exit(1)