使用expect自动化你的shell操作

经常使用跳板机维护远程服务的同学可能会有这么一个苦恼,就是每次连接都需要一连串交互式操作才能连上目标机器,遇到电脑息屏或者退出终端还得再来一遍

比如维护和查询数据库,可能就需要以下操作:

  1. ssh 到远程机器
  2. 输入令牌
  3. 输入密码
  4. 查看机器列表
  5. 选择特定机器
  6. 连接特定数据库客户端
  7. 输入用户名
  8. 输入密码
  9. 选择对应数据库

完成以上 9 个步骤,我们才可以真正开始操作数据库。这个过程如果每天重复多次,实在让人头疼,而 expect 可以帮我们把这些步骤一键搞定

Expect 是什么

Expect 本质上是一个自动化交互工具,最经典的用法就是:

启动一个程序 → 等待程序输出特定内容 → 自动输入 → 再等待 → 再输入。

比如我们手工 ssh 登录一台机器,过程是这样的:

1
2
3
$ ssh root@192.168.1.100        # 发起连接
root@192.168.1.100's password: # 等待输入密码
123456 # 手动输入密码并回车

用 Expect 可以把这个过程完全自动化:

1
2
3
4
5
6
#!/usr/bin/expect

spawn ssh root@192.168.1.100 # 启动 ssh
expect "password:" # 等待出现 password:
send "123456\r" # 自动输入密码并回车
interact # 交还控制权给用户

四个核心概念

spawn:启动程序

1
spawn ssh root@192.168.1.100

相当于在终端执行 ssh root@192.168.1.100,但 Expect 会接管这个进程的输入输出:

1
2
3
4
5
Expect

├── 启动 ssh
├── 读取 ssh 输出
└── 给 ssh 发送输入

expect:等待特定输出

1
expect "password:"

Expect 会持续读取程序输出,直到匹配到 password: 才继续执行下一条命令。比如 ssh 输出了:

1
2
Connecting to 192.168.1.100...
root@192.168.1.100's password:

匹配到 password: 后,流程才往下走。

send:发送输入

1
send "123456\r"

向程序发送 123456 并回车(\r 相当于按下 Enter)。

interact:交回人工控制

1
interact

自动操作完成后,把终端交还给用户,你可以继续手动操作。

串起来看

1
2
3
4
spawn ssh root@192.168.1.100
expect "password:"
send "123456\r"
interact

整个执行流程:启动 ssh → 等待 password: → 自动输入密码 → 进入服务器 → 交还给用户。这就是最经典的 Expect 用法。

示例

还记得开头提到的数据库连接场景吗?9 个步骤手动操作一遍要好几分钟,用 Expect 写成脚本后,一条命令直接搞定:

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
#!/usr/bin/expect

# ---- 配置项 ----
set jump_host "user@jump-server.example.com"
set ssh_pass "my_ssh_password"
set server_id "3"
set db_host "db-server"
set db_port "3306"
set db_user "db_admin"
set db_pass "db_password"
set db_name "my_database"

# 1. ssh 到跳板机
spawn ssh $jump_host

# 2. 输入令牌(令牌是动态的,需要用户手动输入)
expect "token:"
stty -echo
send_user "请输入令牌: "
expect_user -re "(.*)\n"
send "$expect_out(1,string)\r"
stty echo

# 3. 输入密码
expect "password:"
send "$ssh_pass\r"

# 4-5. 等待机器列表,选择目标机器
expect "Please select a server:"
send "$server_id\r"

# 6. 连接数据库客户端
expect "$"
send "mysql -h $db_host -P $db_port\r"

# 7. 输入数据库用户名
expect "Enter user:"
send "$db_user\r"

# 8. 输入数据库密码
expect "Enter password:"
send "$db_pass\r"

# 9. 选择对应数据库
expect "mysql>"
send "use $db_name;\r"

# 交还控制权,开始手动操作
interact

保存为 connect_db.sh,加上执行权限后直接运行即可:

1
2
chmod +x connect_db.sh
./connect_db.sh

脚本会自动完成前面的 9 个步骤,最后通过 interact 把终端交还给你,直接进入数据库开始操作。

说明

示例的第 2 步需要用户手动输入令牌。如果本地安装了 TOTP 令牌生成工具(如 oathtool),也可以在脚本中直接调用来生成,实现完全自动化。