Expect-自动化交互工具
一、概述
1.1 Expect是什么?
Expect 是一个基于 Tcl(Tool Command Language) 的免费编程工具,专门用于实现 自动化交互式任务。它的核心价值是 替代人工手动输入,自动完成需要与终端交互的程序(如SSH登录、密码修改、FTP传输等)的输入响应过程。
典型应用场景:
自动化SSH/FTP远程连接(免去手动输入密码)
批量修改系统用户密码
网络设备(路由器/交换机)的配置脚本化
与需要交互的命令行工具(如
passwd
、ftp
、mysql
等)交互
作者定义:
由Don Libes在1990年开发,最初定义为“实现自动交互功能的软件套件”,目标是让系统管理员可以通过脚本模拟人工输入,完成原本需要手动操作的交互任务(甚至能实现简单的BBS聊天机器人)。
1.2 为什么需要Expect?
常规Shell脚本(如Bash)虽然支持循环、条件判断等控制流,但 无法直接处理需要交互的程序。例如:
当执行
passwd tom
修改用户密码时,系统会提示输入新密码并验证,这些交互必须通过键盘手动输入。若需批量修改多个用户的密码,或通过SSH自动登录远程主机执行命令,Shell无法直接完成这类交互。
Expect的解决方案:
通过监听程序输出的特定提示(如“密码:”“新的密码:”),自动发送对应的输入(如密码字符串),从而实现 无人值守的自动化交互。
1.3 与SSH免交互的其他方式对比
实现SSH远程操控主机的免交互连接,常见方法包括:
SSH密钥对(推荐):通过生成公钥/私钥对,将公钥部署到目标主机,实现无密码登录(最安全且无需额外工具)。
sshpass工具:直接在命令行或脚本中明文传递密码(安全性低,密码可能被
ps
命令捕获)。Expect工具:通过脚本监听SSH的交互提示(如“password:”),自动发送密码(平衡灵活性与安全性,适合复杂交互场景)。
二、环境准备:安装Expect
2.1 检查是否已安装
在Linux系统中,可通过以下命令检查Expect是否已安装:
rpm -q expect # CentOS/RHEL
# 或
dpkg -l | grep expect # Ubuntu/Debian
若未安装,会提示“package expect is not installed”。
2.2 安装方法
根据系统类型选择对应命令:
系统类型 | 安装命令 | 备注 |
---|---|---|
CentOS/RHEL |
| 需root权限 |
Ubuntu/Debian |
| 需联网 |
macOS |
| 需提前安装Homebrew |
源码编译 | 需先安装Tcl 8.6+,再下载Expect源码编译 | 适用于特殊环境 |
三、Expect核心工作原理与基础命令
3.1 Expect的基本工作流程
使用Expect脚本时,其核心流程遵循以下步骤:
启动交互进程:通过
spawn
命令启动需要交互的目标程序(如passwd
、ssh
)。监听预期输出:通过
expect
命令等待目标程序输出的特定提示信息(如“密码:”“新的密码:”)。发送响应内容:当匹配到预期提示后,通过
send
命令向目标程序发送对应的输入(如密码字符串)。结束或继续:进程执行完成后,通过
expect eof
(等待进程结束)或继续监听其他交互。
关键内置命令:spawn
(启动)、expect
(监听)、send
(发送)、exp_continue
(继续匹配)。
3.2 基础命令详解
(1)spawn:启动交互进程
功能:启动一个需要与终端交互的程序(如修改密码、SSH连接)。
语法:
spawn [选项] 命令 [参数]
示例:
spawn passwd tom # 启动修改用户tom密码的交互进程
spawn ssh root@192.168.1.100 # 启动SSH连接远程主机的进程
(2)expect:监听预期输出
功能:等待目标程序输出的特定字符串(提示信息),若匹配则触发后续动作(如发送输入)。
语法:
expect [选项] "模式1" {动作1} "模式2" {动作2} ...
# 支持正则表达式(-re)、超时设置(timeout)等
常见用法:
expect "密码:" { send "123456\r" } # 匹配“密码:”后发送密码并回车
expect {"新的密码:" { send "abc123\r" } # 匹配“新的密码:”后发送新密码timeout { puts "等待超时" } # 超时后执行动作
}
选项说明:
-re
:使用正则表达式匹配(如-re {pass.*:}
匹配以“pass”开头后跟冒号的字符串)。timeout
:设置等待超时时间(单位:秒,默认10秒,可通过set timeout XX
全局设置)。
(3)send:发送响应内容
功能:向当前交互进程发送指定的字符串(模拟用户键盘输入)。
语法:
send "要发送的内容\r" # \r表示回车键(必需,模拟按下Enter)
特殊字符:
\r
:回车(Enter键)\n
:换行\t
:制表符
示例:
send "123456\r" # 发送密码“123456”并回车
send "ls -l\r" # 发送命令“ls -l”并执行
(4)exp_continue:继续匹配
功能:当需要在一个expect
块中匹配多个模式时,通过该命令保持监听状态,不退出当前expect
块。
典型场景:处理需要多次输入的情况(如密码不符合要求需重新输入)。
示例:
expect {"新的密码:" { send "123456\r" # 发送新密码exp_continue # 继续监听下一个提示(如“重新输入密码:”)}"重新输入新的密码:" { send "123456\r" # 发送确认密码}eof # 进程结束时退出
}
(5)其他辅助命令
send_user:向标准输出(屏幕)打印信息(类似Shell的
echo
),用于调试或日志输出。send_user "正在修改用户密码...\n"
exp_internal 1:开启内部调试模式,显示详细的匹配过程(调试用,输出到屏幕)。
log_file:记录完整的交互过程到日志文件(用于问题排查)。
log_file -a /var/log/expect.log # 追加记录日志
3.3 变量与流程控制
(1)变量定义与使用
普通变量:通过
set
定义,通过$变量名
或puts $变量名
引用。set username "tom" # 定义变量 set password "123456" puts $username # 输出变量值 send "$password\r" # 在send中使用变量
位置参数:通过
lindex $argv
获取脚本传递的参数(类似Shell的$1
、$2
)。set user [lindex $argv 0] # 第一个参数(如脚本调用时传入的用户名) set pass [lindex $argv 1] # 第二个参数(密码) puts "用户名: $user, 密码: $pass"
特殊变量:
$argc
:脚本接收的参数总个数$argv0
:当前脚本的名称
(2)条件判断(if语句)
语法:
if {条件表达式} {# 条件成立时执行的命令
} elseif {条件} {# 其他条件
} else {# 默认命令
}
示例:
if {$argc < 2} {send_user "错误:需传入用户名和密码两个参数!\n"exit 1
} else {set user [lindex $argv 0]set pass [lindex $argv 1]
}
(3)循环语句
for循环:
for {set i 1} {$i <= 5} {incr i} {puts "当前数字: $i" }
while循环:
set i 1 while {$i <= 3} {puts "循环次数: $i"incr isleep 1 }
四、完整脚本示例
4.1 案例1:修改用户密码(基础版)
需求:通过Expect脚本自动修改用户tom
的密码为123456
(假设原密码不符合要求需多次输入)。
脚本内容(change_pass.exp):
#!/usr/bin/expect
# 自动修改用户密码脚本spawn passwd tom # 启动修改密码进程
expect {"新的 密码:" { send "123456\r" # 发送新密码exp_continue # 继续监听(可能要求确认密码)}"重新输入新的 密码:" { send "123456\r" # 发送确认密码}"密码:" { # 部分系统可能先提示原密码(若需原密码则调整)send "oldpass\r" # 若有原密码要求,替换为实际原密码exp_continue}eof # 进程结束时退出
}
使用方法:
chmod +x change_pass.exp
./change_pass.exp
4.2 案例2:SSH自动登录并执行命令
需求:自动登录远程主机192.168.1.100
,执行ifconfig ens32
命令并获取网卡信息。
脚本内容(ssh_auto.exp):
#!/usr/bin/expect
# SSH自动登录并执行命令set host "192.168.1.100"
set user "root"
set pass "123456"spawn ssh $user@$host # 启动SSH连接
expect {"yes/no" { send "yes\r" # 首次连接时确认主机密钥exp_continue # 继续监听密码提示}"password:" { send "$pass\r" # 发送密码}
}
expect "#" # 等待出现命令行提示符(如root用户的#)
send "ifconfig ens32\r" # 发送查看网卡的命令
expect "#" # 等待命令执行完成
send "exit\r" # 退出SSH会话
expect eof # 等待进程结束
使用方法:
chmod +x ssh_auto.exp
./ssh_auto.exp
五、Shell脚本调用Expect的方法
5.1 方法1:通过expect -c
内联执行
在Shell脚本中直接嵌入Expect代码(适合简单场景):
#!/bin/bash
# 批量修改多个用户密码(通过Shell循环调用Expect)for user in tom alice bob; doexpect -c "spawn passwd $userexpect \"新的 密码:\"send \"123456\\r\"expect \"重新输入新的 密码:\"send \"123456\\r\"expect eof"
done
5.2 方法2:通过<<EOF
嵌入脚本(推荐)
使用Here Document将Expect脚本嵌入Shell脚本(更清晰且易维护):
#!/bin/bash
# 批量上传文件到远程主机(通过Expect免交互SCP)for ip in 192.168.1.{112..113}; do/usr/bin/expect << EOFset timeout 15spawn scp /local/script.sh root@$ip:/opt/expect {"yes/no" { send "yes\\r"; exp_continue }"password:" { send "123456\\r" }}expect eof
EOF
done
注意事项:
必须使用
/usr/bin/expect
的绝对路径(避免环境变量问题)。<<EOF
与EOF
之间的内容需严格符合Expect语法(缩进不影响执行)。
六、生产环境应用案例:批量采集CPU信息
6.1 需求背景
假设有一个脚本cpuinfo.sh
,用于统计服务器的CPU核心数、逻辑CPU数、超线程状态等信息。现需将该脚本批量分发到多台主机(如192.168.200.112~113
),并自动执行以收集结果。
6.2 实现步骤
步骤1:编写CPU信息统计脚本(cpuinfo.sh)
#!/bin/bash
echo "逻辑CPU总数: $(grep -c '^processor' /proc/cpuinfo)"
echo "物理CPU颗数: $(grep 'physical id' /proc/cpuinfo | sort -u | wc -l)"
echo "每颗物理CPU的核心数: $(grep 'core id' /proc/cpuinfo | sort -u | wc -l)"
echo "每颗物理CPU的逻辑CPU数: $(grep 'siblings' /proc/cpuinfo | sort -u | awk -F: '{print $2}')"
if [ $(grep 'siblings' /proc/cpuinfo | awk -F: '{print $2}' | head -1) -gt $(grep 'core id' /proc/cpuinfo | sort -u | wc -l) ]; thenecho "超线程已启用"
elseecho "超线程未启用"
fi
步骤2:编写Expect脚本实现分发与执行
(1)分发脚本到远程主机(auto_upload.exp)
#!/usr/bin/expect
# 自动上传文件到远程主机if { $argc != 3 } {puts "用法: expect auto_upload.exp <本地文件> <目标主机> <远程目录>"exit 1
}
set local_file [lindex $argv 0]
set host [lindex $argv 1]
set remote_dir [lindex $argv 2]
set pass "123456"spawn scp $local_file root@$host:$remote_dir
expect {"yes/no" { send "yes\r"; exp_continue }"password:" { send "$pass\r" }
}
expect eof
(2)在远程主机执行脚本(auto_run.exp)
#!/usr/bin/expect
# 自动在远程主机执行命令if { $argc != 2 } {puts "用法: expect auto_run.exp <目标主机> <要执行的命令>"exit 1
}
set host [lindex $argv 0]
set cmd [lindex $argv 1]
set pass "123456"spawn ssh root@$host "$cmd"
expect {"yes/no" { send "yes\r"; exp_continue }"password:" { send "$pass\r" }
}
expect eof
(3)Shell脚本整合流程(batch_collect.sh)
#!/bin/bash
# 批量分发并执行CPU信息采集# 1. 分发脚本到所有目标主机
for ip in 192.168.200.112 192.168.200.113; do/usr/bin/expect << EOFset timeout 10spawn scp cpuinfo.sh root@$ip:/opt/expect {"yes/no" { send "yes\\r"; exp_continue }"password:" { send "123456\\r" }}expect eof
EOF
done# 2. 在所有目标主机执行脚本
for ip in 192.168.200.112 192.168.200.113; do/usr/bin/expect << EOFset timeout 20spawn ssh root@$ip "bash /opt/cpuinfo.sh"expect {"password:" { send "123456\\r" }}expect eof
EOF
done