需要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
将所要进行的命令逐行写在一个文本文件中,与脚本放在同级目录下,即可实现一步操作。