一、expect 简介
expect 是一个自动交互功能的工具,可以实现自动登录,不必手动输入密码(Password)、确认(Yes)等交互操作。
expect 是开了一个子进程,通过spawn来执行shell脚本,except监测到脚本的返回结果,并发送交互输入内容(send)
二、except 安装
1. Ubuntu 安装
sudo apt-get -y install tcl expect
2. CentOS 安装
yum -y install expect
3. MacOS 安装
brew install expect
安装完成后的目录为
# which expect
/usr/bin/expect
三、except 使用
1. expect 使用示例
#!/usr/bin/expect
#
# mimvp.com
# 2016.10.12
set timeout 30
set passwd "mimvp.com"
spawn ssh -l mimvp 123.57.78.100
expect "password:"
send "$passwd\n"
interact
说明:
#!/usr/bin/expect 表示使用expect的shell交互模式,由expect来解释执行的而不是由bash,所以代码的语法和shell脚本也是不一样的
set timeout 30 表示对变量timeout赋值,为30秒。如果执行的shell命令耗时长可以设置超时时间长一些,默认timeout为10秒
set passwd "mimvp.com" 表示对变量passwd赋值,密码供后面的引用。特殊字符需要转义,例如 "\$mimvp.com"
spawn ssh -l mimvp 123.57.78.100 表示在expect下执行shell脚本,例如本例的 ssh 远程登录服务器
expect "password:" 表示对执行shell脚本的返回字符串进行判断,是否包含"password:"字段,一般可写做 "*assword:"
send "$passwd\n" 表示如果expect监测到了包含的字符串,将输入send中的内容,\n相当于回车,有的也写做\r
interact 表示留在新开的子进程内,可以继续输入,否则将退出子进程回到shell中。例如 ssh登录到某台服务器上,只有加了interact才可以留在登录后的机器上进行操作
2. expect 命令行参数
[lindex $argv n]获得参数下标为 index = n 的参数(index从0开始计算)
$argc为命令行参数的个数
[lrange $argv 0 0]表示第一个参数
[lrange $argv 0 3]表示第1到第3个参数
用法示例:
#!/usr/bin/expect
#
# mimvp.com
# 2016.10.12
set timeout 30
set passwd "mimvp.com"
set ipaddr [lrange $argv 0 0]
#set ipaddr [lindex $argv 0]
spawn ssh -l root $ipaddr
expect "password:"
send "$passwd\n"
interact
## ./conn_server.sh 123.57.78.100
上例里,执行语句为
./conn_server.sh 123.57.78.100
其中:
./conn_server.sh 是脚本文件名
123.57.78.100 是服务器IP地址,set ipaddr [lrange $argv 0 0] 获取第一个参数IP地址,并赋值给 ipaddr
spawn ssh -l root $ipaddr 表示使用IP地址,进行ssh登录
例如:scp_service.sh文件,可以 ./scp_service.sh -rm来执行,这时 -rm 是赋值的第一个参数
set option [lindex $argv 0](获得第一个参数-rm 存到变量option中,参数是的index是从0开始计算的)
3. expect 判断语句
expect支持if语句 if...elif...else...
if { 条件1 } {
条件1执行语句
} elif { 条件2 } {
条件2执行语句
} else {
其他情况执行语句
}
用法示例:
#!/usr/bin/expect
#
# mimvp.com
# 2016.10.12
set timeout 30
set passwd "mimvp.com"
if { [llength $argv] < 2 } {
puts "Usage:"
puts "$argv0 username ipaddr"
exit 1
}
#set username [lindex $argv 0]
#set ipaddr [lindex $argv 1]
set username [lrange $argv 0 0]
set ipaddr [lrange $argv 1 1]
spawn ssh -l $username $ipaddr
expect "password:"
send "$passwd\n"
interact
$ ./conn_server.sh mimvp## error
Usage:
./conn_server.sh username ipaddr
$ ./conn_server.sh mimvp 123.57.78.100## success
说明:
1. if 的条件用{}来包含条件
2. if 和 后面的{}必须有空格隔开
3. 两个花括号之间必须有空格隔开,比如if {} {},否则会报错 expect:extra characters after close-brace
3. 使用{来衔接下一行,所以if的条件后需要加左花括号{
4. else不能单独放一行,所以else要跟在}后面
except 实现 scp 拷贝
#!/usr/bin/expect
#
# mimvp.com
# 2016.10.12
set timeout 30
set passwd "mimvp.com"
if { [llength $argv] < 2 } {
puts "Usage:"
puts "$argv0 local_file remote_path"
exit 1
}
set local_file [lindex $argv 0]
set remote_file [lindex $argv 1]
set passwd_error 0
spawn scp $local_file $remote_file
expect {
"*assword:*" {
if { $passwd_error == 1 } {
puts "passwd is error"
exit 2
}
set timeout 100
set passwd_error 1
send "$passwd\n"
exp_continue
}
"*es/no)?*" {
send "yes\n"
exp_continue
}
timeout {
puts "connect is timeout"
exit 3
}
}
4. expect {} 多行期望
有时执行shell后预期结果是不固定的,有可能是询问是yes/no,有可能是去输入密码,所以可以用expect{}(比如sudo命令,第一次使用sudo时需要输入密码,但是它有5分钟的有效时间,5分钟内是不需要再去输入的)
花括号内放多行语句,从上至下匹配,匹配到哪个expect,则执行哪句。
这里如果匹配到第一行会执行第一行;然后第一行的执行结果如果匹配到第二行也会执行第三行;
如果某一行没有匹配到会向下寻找匹配到的那一行进行执行)
注意:多行的expect的{后不要跟语句,否则读不到这条,需要换行后去写具体的期望值和操作。
#!/usr/bin/expect
#
# mimvp.com
# 2016.10.12
expect {
"*assword" {send "mimvp.com\n"}
"*es/no)?*" {send "yes\n"}
"exit" {send "exit\n"}
exp_continue
}
说明:exp_continue表示继续执行下面的expect。
四、shell中调用expect实现自动登录
通过在shell脚本中,执行expect脚本的方式来实现的。
当然可以将shell中定义的一些变量传递给expect脚本作为参数输入。
示例1:shell 调用 expect 脚本,间接调用
#!/bin/bash
#
# mimvp.com
# 2016.10.12
script_tmp="/Users/homer/script_tmp"
if [ ! -d ${script_tmp} ]; then
echo "${script_tmp} is not found"
mkdir -p ${script_tmp}
cd ${script_tmp}
else
cd ${script_tmp}
fi
## expect file
#./scp_remote_server.sh
/Users/homer/script/scp_remote_server.sh
示例2:shell 嵌套 expect 脚本,直接调用(推荐)
#!/bin/bash
#
# mimvp.com
# 2016.10.12
username='mimvp-guest'
passwd='mimvp.com'
passwd2='\$mimvp.com'
/usr/bin/expect <
set timeout 30
spawn /usr/bin/htpasswd -c /etc/squid/passwd $username
## first password
expect {
"*es/no:" { send "yes\n"; exp_continue }
"*assword:" { send "$passwd\n; exp_continue" }
}
## second password to confirm
expect "*assword:"
send "$passwd\n"
spawn ssh mimvp@123.57.78.100
expect {
"*es/no" { send "yes\r"; exp_continue }
"*assword" { send "$passwd2\r" }
}
expect "*#"
send "cd /home/script/\r"
#expect "*#"
#send "svn up\r"
expect "*#"
send "ls -l\r"
expect "*#"
send "exit\r"
#interact
expect eof
EOF
本段脚本,实现了两个功能:
功能1,使用htpasswd命令,输入密码,并再次确认密码
功能2,远程登录服务器,并svn up下载代码,并退出远程服务器
以上脚本,君测试成功!
说明:经过这次尝试些expect,给我的感觉是expect对格式的要求比较高,比如花括号之间必须有空格啊之类的,所以如果有报错,大家可以仔细观察一下是不是语法格式错误了。
expect 更多语法
1. expect中的判断语句
if { condition } {
# do your things
} elseif {
# do your things
} else {
# do your things
}
expect中没有小括号(),所有的if/else, while, for的条件全部使用大括号{}, 并且{ 与左边要有空格,否则会报错。另,else 不能单独占一行,否则会报错。
2. 字符串比较
if { "$node" == "apple" } {
puts "apple"
} elseif { "$node" == "other" } {
puts "invalid name"
exit 70
} else {
puts "asd"
}
对比string,使用==表示相等, !=标示不相等。
3. switch 语句
switch $location {
"apple" { puts "apple" }
"banana" { puts "banana" }
default {
puts "other"
}
记得左大括号{ 的左边要有空格,否则会报错
4. 读取用户输入
expect_user -re "(.*)\n"
send_user "$expect_out(1, string)\n"
expect_user -re 表示正则表达式匹配用户按下回车前输入的所有字符
expect_out(1, string) 表示第一个匹配的内容,即回车前所有字符
expect_out(buffer) 所有的buffer内容
5. break && continue
如c中一样,expect一样可以使用break && continue, 并且功能相同。注:只能用在循环中。
6. 定义交互命令
# stick control + z in variable
set ControlZ \032
# stick control + c in variable
set ControlC \x03
# define string embedded ctrl-z && tab
set oddword foo\032bar\tgorp
五、在远程服务器上配置ssh信任
网上有很多教程,推荐米扑博客:Linux两台主机之间建立信任 (非常经典,验证成功)
感觉长期的话应该比写expect方便,但是我觉得写脚本的话还是最好不要总去操作其他地方,
所以这里我就用expect自己来写的(当然也是想练习一下写expect)
参考推荐: