需要ssh上多台主机进行相同操作,重复劳作是枯燥的,所以写个shell脚本实现。用到了expect。
网上有不少expect的教程,都大同小异,没有说得很详细的。求人不如求己,man expect瞅了瞅,再看看tcl语法,就差不多了。expect用的是tcl语法,所以要用expect实现一些功能,需要学一些tcl的知识。这里有个不错的网站学习tclhttp://www.yiibai.com/tcl/
先贴一个expect实现scp传输的实例:
#!/usr/local/bin/expect set timeout 10 set host [lindex $argv 0] set username [lindex $argv 1] set password [lindex $argv 2] set src_file [lindex $argv 3] set dest_file [lindex $argv 4] spawn scp $src_file $username@$host:$dest_file expect { "(yes/no)?" { send "yes\n" expect "*assword:" { send "$password\n"} } "*assword:" { send "$password\n" } } expect "100%" expect eof 可以简单地说,expect是一个阻塞的switch,sp awn执行一个命令后,expect会匹配返回值,并执行相应的操作。
1.expect格式
expect { "string" #-re可以正则匹配字符串 { command } status { command } } 或者expect "string" {command}(注意,expect右边必须有值,把括号写在下一行会报错!)
其中command可以是tcl语法命令,如puts,也可以是expect命令,如send,exp_continue,exit等等;而status是指expect的状态信息,如timeout,connected。
2.tcl语法
tcl是一种简单的脚本语言,这里简单介绍一些需要用到的语法。
puts 打印变量或字符串,调试必备
set 赋值,sethost [lindex $argv0] 就是将参数0赋值给变量host,其中,[] 括起命令,执行括号内命令后返回结果,lindex是取列表中的某个参数,$argv则是参数列表。
while { } {command} while循环,和bash类似,while后面要留一个空格,右括号后面再留一个空格,我就是在这里遇到了莫名的报错。。
3.timeout&exp_continue
set timeout 10是设置超时时间。如果过了超时时间控制台仍旧没有返回,或者返回的都不符合期待的字符串,就会超时,如果设置了timeout的动作,就会执行该动作,否则程序继续向下执行。
写了一个简单的timeout如下
set timeout 1 spawn ssh *** expect{ timeout {exp_continue;puts timeout\n} } 设置超时时间为1秒,ssh一般来说1秒钟还没有返回,所以会触发timeout状态。然而这段代码并没有打印出timeout,原因是exp_continue,它会重置定时器,继续等待一个超时时间,类似于while循环中的continue会跳过其后的代码开始下一次循环,exp_continue也会跳过其后的代码开始下一次等待。
4.附源码
#!/usr/local/bin/expect set timeout 10 set host [lindex $argv 0] set username [lindex $argv 1] set password [lindex $argv 2] set filename [lindex $argv 3] set file_data [open "./$filename" r] spawn ssh $username@$host expect { "(yes/no)?" { send "yes\n" expect "*assword:" { send "$password\n" expect "try again" {send_user "password is wrong\n";exit} } } "*assword:" { send "$password\n" expect { "try again" {send_user "password is wrong\n";exit} #connected send_user "connected" } } } while {[gets $file_data command]>=0} { send "$command \n" expect "*#" } send "exit\n" close $file_data 将所要进行的命令逐行写在一个文本文件中,与脚本放在同级目录下,即可实现一步操作。