使用expect构建命令行自动化交互程序

背景介绍

expect 是由Don Libes基于Tcl( Tool Command Language )语言开发的,主要应用于自动化交互式操作的场景,借助Expect处理交互的命令,可以将交互过程如:ssh登录,ftp登录等写在一个脚本上,使之自动化完成。尤其适用于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率

命令

在tcl语法中注释放在代码的后面会有问题,需要写在独立的行

expect

说明: 等待直至满足以下任意条件

  1. 某一规则匹配到衍生进程得输出
  2. 超时
  3. 遇到文件结尾

expect是面向整个输出缓冲区的,并非面向行

选项

-timeout 等待时间,expect内使用timeout用于局部覆盖全局timeout

匹配模式

1.字符串匹配
1
2
3
4
5
6
7
expect {
busy {puts busy\n ; exp_continue}
failed abort
"invalid password" abort
timeout abort
connected
}
2.正则匹配(tcl语法)
1
2
3
4
5
6
expect {
busy {puts busy\n ; exp_continue}
-re "failed|invalid password" abort
timeout abort
connected
}

缓冲区

匹配后结果保存在变量expect_out(buffer)中,对于正则模式则最多保存9个正则子匹配结果,分别保存在expect_out(1,string)expect_out(9,string)

假如进程输出了”abcdefgh\n”,对于expect “cd”来讲,等同于执行

1
2
set expect_out(0,string) cd
set expect_out(buffer) abcd

其中”efgh\n”留在输出缓冲

又比如进程输出”abbbcabkkkka\n”,对于expect -indices -re “b(b).(k+)”来讲等同于执行

1
2
3
4
5
6
7
8
9
10
11
set expect_out(0,start) 1
set expect_out(0,end) 10
set expect_out(0,string) bbbcabkkkk
set expect_out(1,start) 2
set expect_out(1,end) 3
set expect_out(1,string) bb
set expect_out(2,start) 10
set expect_out(2,end) 10
set expect_out(2,string) k
set expect_out(buffer) abbbcabkkkk
其中"a\n"被留在输出缓冲区

衍生进程的缓冲区expect_out(spawn_id)

exp_continue

通常情况下匹配到结果并执行完操作后会返回,使用exp_continue允许expect自身继续循环执行而不是执行后直接返回,当前所在expect继续执行

stty

stty 用于控制终端
示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
expect {
Password: {
# 关闭输入回显 防止密码外露
stty -echo
send_user "password (for $user) on $host: "
# 接受用户输入
expect_user -re "(.*)\n"
send_user "\n"
# 将password变量设置未用户输入
set password $expect_out(1,string)
stty echo # 打开输入回显
exp_continue
}
}

interact

interact 将进行的交互控制权交给用户

send

send 向当前进程发送字符串
send_user 向用户发送字符串

spawn

spawn 创建新进程运行命令
示例: spawn ssh sxk@xxx.com
成功后spawn_id变量被设置为衍生进程id

set

set 设置变量

1
2
# 设置EMAIL_PWD变量为第一个传参
set EMAIL_PWD [lindex $argv 0]

调试

方法1: 使用-d选项进行调试

1
expect -d expect_script

方法2: 在脚本中使用exp_internal命令打开调试模式

1
2
#1开启 0关闭
exp_internal 1

最佳实践

与shell联动

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
username=$1
password=$2
host=$3
/usr/local/expect <<-EOF
set timeout 10
spawn ssh $username@$host
expect {
"password" { send "$password\r" }
}
EOF

成品展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
➜ cat ~/.relay.expect
#!/usr/bin/expect
# relay用户名
set USER [lindex $argv 0]
# relay EMAIL_PWD
set EMAIL_PWD [lindex $argv 1]
# [可选]开发机地址, 如:user@host
set HOST [lindex $argv 2]
# [可选]开发机密码
set PASSWORD [lindex $argv 3]
# 自动调整窗口大小
trap {
set rows [stty rows]
set cols [stty columns]
stty rows $rows columns $cols < $spawn_out(slave,name)
} WINCH
# 登录relay
spawn ssh $USER@relay.baidu-int.com
# 打开调试模式
#exp_internal 1
# relay EMAIL_PWD + Hi手势认证
expect {
-re "Please input user's password:" {
send "$EMAIL_PWD\r"
send_user "\n\n请打开手机Hi,进行手势认证!\n"
-timeout 10
exp_continue
}
# 自动登录开发机
-re "-bash-baidu-ssl" {
if { "$HOST" != "" } {
send "ssh --silent $HOST\r"
# [可选]自动开发机输入密码
if { "$PASSWORD" != "" } {
expect {
-timeout 2
-re "password:" { send "$PASSWORD\r" }
}
}
}
expect {
-timeout 2
-re "pandora" {
send "listapp|grep user\r"
# expect 正则使用得是tcl https://www.yiibai.com/tcl/tcl_regular_expressions.html
# 使用pcre语法无效
expect -re {[0-9]+.bdapp-resbox-user-[a-z]*} {
send "matrix_jail $expect_out(0,string)\r"
send "cd /home/work/orp/log\r"
}
}
}
}
}
# 关闭调试模式
#exp_internal 1
#保持交互式访问
interact
exit

参考资料

expect man page
expect自动交互脚本
shell编程之expect用法