expect
假如有两台主机 A 和 B,希望从 A 主机 ssh 到 B 主机,然后在 B 主机执行一些命令,可行的命令是 ssh usrname@hostB "command"
,每次都要输入密码,同时并不能执行一些复杂的逻辑或命令,有没有办法能把复杂命令自动化呢?
expect 是用来进行自动化控制和测试的软件工具。我们运行命令、脚本或程序时,这些命令、脚本或程序都需要手工从终端输入运行的指令。利用expect,我们可以根据程序的输出提示,模拟标准输入,替代人工敲命令提供给程序,从而实现自动化交互执行。
# 例如一个远程登录到主机上执行相关命令的 expect 脚本可以这么写
#!/usr/bin/expect
set host "xx.xx.xx.xx"
set username "xxx"
set password "xxx"
spawn ssh -o "StrictHostKeyChecking no" $username@$host
expect "*assword*"
send "$password\r"
expect "*$*"
send "command\n"
expect "*$*"
send "exit\n"
expect eof
# 你也许感觉出来了整个执行流程的模式,expect 顾名思义,就是期待一个什么输出,然后执行一个动作。
通常场景的命令有:
命令 | 作用 |
---|---|
send | 接收一个字符串参数,并将该参数发送到进程 |
expect | 和 send 相反,通常用来等待一个进程的反馈,我们根据进程的反馈,再发送对应的交互命令 |
spawn | 启动一个进程,spawn 后的 send 和 expect 命令都是和使用 spawn 打开的进程进行交互 |
interact | 用于退出自动化,进入人工交互。比如我们使用spawn、send 和 expect 命令完成了 ftp 登陆主机,执行下载文件任务,在文件下载结束以后,希望仍然停留在 ftp 命令行状态,以便手动执行后续命令,使用 interact 命令就可以很好的完成这个任务 |
send_user | 接收一个字符串参数,向用户发送数据 |
set timeout xx | 设置超时时间,单位是 s,默认是 10 |
set send_slow {10 0.001} | 设置输入频率,每10个字符间隔1ms |
sleep | 自动执行受限于系统响应等情况,有时会插入该语句等待执行完成,再执行下面的语句 |
exp_continue |
expect {
pattern {
action
} pattern {
action
} pattern {
action
}
}
最后一个 expect 语句后面的 send 语句都不执行,为了让所有 send 语句都执行,可以用一个不带 send 的 expect 语句结尾。
expect "*$*"
send "pwd\n"
send "ls -l\n"
send_user "suscess\n" # 执行
expect "*$*" # 可添加一个 expect
如果 spawn
了多个进程,怎么区分 send
命令发送给哪个进程呢?
send 命令可以带一些选项支持这些操作,如
spawn telnet ip port
set TELNETID $spawn_id
send -i $TELNETID 用来向不同进程发送命令,是进行多程序控制的关键参数
send -s "xxx" s 代表 slowly,也就是控制发送的速度,这个参数使用的时候要与expect中的变量send_slow相关联
复杂的 shell 指令执行
需要注意的是 expect 无法识别 shell 的语法,所以如果用了复杂的 shell 命令,会报如下错误 xxx : no such file or directory
。如果想要 expect 执行复杂的 shell 命令,可以这么做 spawn bash -c {your shell commands}
。
命令行参数获取
#!/usr/bin/expect
# 判断输入的参数是否为 3 个,如果不为 3 个,就打印错误信息,退出该程序。
if { $argc != 3 } {
puts stderr "Usage: test1 host-address username host-password\n"
exit 1
}
set timeout 60
set host [lindex $argv 0] # 命令行参数获取,从真实的参数开始计算,expect xx.exp ARG0 ARG1 ...
set name [lindex $argv 1]
set password [lindex $argv 2]
##set host "192.168.0.4"
##set name “root”
##set password "cluster"
# root 用户的 rsa key 放在 /root/.ssh 中,其他用户则放在 /home/$name/.ssh
if { $name == "root"} {
spawn scp /$name/.ssh/id_rsa.pub $name@$host:/tmp
} else {
spawn scp /home/$name/.ssh/id_rsa.pub $name@$host:/tmp
}
# 等待上个命令的响应
expect {
"(yes/no)?" {
send "yes\n"
expect "assword:"
send "$pasword\n"
} "assword:" {
send "$password\n"
}
}
# 输入密码后,拷贝成功,出现 100% 字符串,作为预期响应
expect "100%"
# 调用 ssh 以 $name 用户名登录到 $host 上
spawn ssh $name@$host
expect "assword:"
send "$password\n"
expect ":~#"
# 将刚刚拷贝的 rsa key 添加到用户的 home 目录下的 ./ssh/authorized_keys
if { $name == "root"} {
send "cat /tmp/id_rsa.pub >> /root/.ssh/authorized_keys\n"
} else {
send "cat /tmp/id_rsa.pub >> /home/$name/.ssh/authorized_keys\n"
}
expect ":~#"
# 操作成功后,退回 SSH 客户端机器
send "exit\n"
expect "#"
# 下面将测试能否自动登录,不用输入密码
spawn ssh $name@$host
expect {
"Welcome" {
send_user "Auto login the server successfully!"
} "assword:" {
send_user "failed to login the server!"
}
}
send "ls\n"
expect ":~#"
# 退出 $host
send "exit\n"
# 程序结束
expect eof
函数
# definition
proc funcname {para1 para2} {
command
command
}
funcname para1 para2
# if
if {xxx} {
...
} else {
...
}
# for
for {set i 0} {$i < xxx} {incr i} {
....
}
# foreach
# while