expect 是用来进行自动化控制和测试的工具。主要是和交互式软件 telnet ftp passwd fsck rlogin ssh tip 等进行自动化的交互。Linux 交互命令中经常需要输入 yes/no 或者 password 等操作,模拟这些输入,就可以使用 expect 脚本。expect 是由 tcl 语言演变而来的。简单地说,expect 是一个工具,可以根据用户设定的规则和系统进程进行自动化交互,例如远程登陆的密码输入、自动化的执行远程命令。
一个非常典型的使用场景就是一般在公司中都会使用 relay 来连接管理服务器的远程连接和使用,通常会需要在 SSH 登录的时候使用用户名和密码,甚至需要二步验证来增强安全性,但是如果不想每一次都重复输入用户名和密码就可以使用 expect 命令来快速实现登录。
安装
Debian/Ubuntu/Linux Mint 系安装非常简单
apt install expect
关键命令
expect 下几个非常重要的指令:
- spawn: 启动进程(由 spawn 启动的进程的输出可以被 expect 所捕获)
- expect: 从进程接收字符串,期望获得字符串
- send: 向进程发送字符串,用于模拟用户的输入,注意一定要加
\r
回车 - interact: 用户交互
- sleep n: 使脚本暂停给定的秒数
spawn 指令用来开启比如 Shell, FTP, SSH ,SCP 等等的交互指令。
命令行参数
$argc,$argv 0,$argv 1 ... $argv n
argc 表示命令行参数个数,后面分别表示各个参数项,0 表示第一个参数,1 表示第二个参数,以此类推,可以通过 lindex 获取对应参数值 (lindex $argv 0)。
if {$argc < 2} {
puts stdout "$argv0 err params\n"
exit 1
}
if {[llength $argv] == 0} {
puts stdout "need server name as param"
exit 1
}
输入输出
puts stderr "Usage: $argv0 login passwaord.n "
puts "hello world"
puts stdout "1234"
变量赋值
set argv [lindex $argv 0]
set user "einverne"
set count 3
set ip "192.168.2.1"
复合指令
比如要将脚本的参数赋值给变量
set my_var [lindex $argv 0]
命令调用
spawn ssh $user@$ip
spawn 启动一个进程,进程执行 ssh 命令,程序后面可以通过 expect/send 和新起的进程进行交互
分支语句
单一分支语法:
expect "hello" {send "you said hello"}
多分支模式语法:
expect {
"lilei" {send "hello lilei"; exp_continue}
"hanmeimei" {send "hello hanmeimei"; exp_continue}
"how do you do ?" {send "how do you do ?"}
}
switch 分支
switch -- $var {
{
}
{
}
{
}
}
if else 分支
set NUM 1
if { $NUM < 5 } {
puts "\Smaller than 5\n"
} elseif { $NUM > 5 } {
puts "\Bigger than 5\n"
} else {
puts "\Equals 5\n"
}
循环
while 循环语法
#!/usr/bin/expect -f
set NUM 0
while { $NUM <= 5 } {
puts "Number is $NUM"
set NUM [ expr $NUM + 1 ]
}
for 循环
for {set NUM 0} {$NUM <= 5} {incr NUM} {
puts "\nNUM = $NUM"
}
puts ""
自定义方法
定义
proc myfunc { TOTAL } {
set TOTAL [expr $TOTAL + 1]
return "$TOTAL"
}
使用
set NUM [myfunc $NUM]
使用正则表达式判断
if {[regexp {^[0-9]+$} $NUM]} {
Do something
} else {
Exit
}
其他表示
if {![regexp {\D+} $NUM]}
if {![string match {[^0-9]+} $NUM]}
实例
登录远程服务器并创建文件夹
#!/usr/bin/expect // 告诉系统脚本的执行方式
set timeout -1 // 等待超时时间, -1 为无限制等待
spawn ssh root@192.168.2.1 // spawn 执行 expect 内部命令,给 ssh 运行进程加壳,传递交互指令
expect { // expect 是 expect 内部命令,判断上次输出结果是否包含,如果有则立即执行操作
"password" {send "123456\r";} // send 执行交互动作,与手工输入密码等效
"yes/no" {send "yes\r";exp_continue}
}
expect "root" {send "mkdir testFolder\r"}
expect eof
exit
expect eof
等待结束标志,spawn 启动的命令在结束时会产生一个 eof 标志。
带参数的脚本
如果脚本依赖外部输入,比如有输入参数,那么可以在后面添加参数:
./expect.ex 192.168.2.1 123456
脚本可以首先用 set 给变量赋值
#!/usr/bin/expect
set ip [lindex $argv 0]
set password [lindex $argv 1]
set timeout -1
spawn ssh root@ip
expect {
"password" {send "$password\r";}
"yes/no" {send "yes\r";exp_continue}
}
expect "root" {send "mkdir test1\r"}
send "exit\r"
expect eof
exit
等待手动操作
登录远程服务器之后使用 interact
来等待手动操作,比如:
./expect.ex 192.168.2.1 123456
脚本:
#!/usr/bin/expect
set ip [lindex $argv 0]
set password [lindex $argv 1]
set timeout -1
spawn ssh root@$ip
expect {
"password" {send "$password\r";}
"yes/no" {send "yes\r";exp_continue}
}
interact // 完成后保持交互状态,把控制权交给控制台
注意最后的 interact
总结
expect 在 Linux 运维中非常有用,可以用来多机器重启,远程 copy 等等场景,shell 中受限于密码输入的操作,可以通过 expect 来节省很多工作。
涉及到脚本地址