介绍
在这个实验中,你将学习如何使用 Linux 的 expect 命令来自动化交互式命令行应用程序。expect 命令是一个强大的自动化工具,它允许脚本与需要用户输入的程序进行交互,例如 SSH、FTP 以及其他交互式程序。
完成这个实验后,你将能够:
- 了解
expect命令的目的和基本语法 - 创建脚本来自动化 SSH 登录
- 在你的
expect脚本中处理各种提示和响应
expect 命令可以显著减少重复任务的手动干预,使系统管理和日常任务更有效率。你将从安装和探索 expect 的基本语法开始,然后逐步创建脚本来自动化 SSH 登录并处理各种交互式提示。
理解 expect 命令及其基本语法
Linux 中的 expect 命令允许你自动化通常需要用户输入的交互式命令行程序。这对于自动化登录、文件传输或任何程序提示输入的情况特别有用。
安装 expect
首先,让我们验证系统上是否安装了 expect 包。打开你的终端并运行:
which expect
如果已经安装了 expect,你将看到它的路径(例如 /usr/bin/expect)。如果没有,你需要安装它:
sudo apt-get update
sudo apt-get install -y expect
理解基本 expect 语法
expect 命令使用基于 Tcl (Tool Command Language) 的脚本语言。expect 脚本的基本结构包括以下命令:
spawn:启动一个要交互的进程expect:等待来自派生进程的特定输出send:向派生进程发送输入set timeout:设置等待预期输出的时间
让我们创建一个简单的 expect 脚本来演示这些概念。打开一个文本编辑器,并在你的项目目录中创建一个名为 hello.exp 的文件:
cd ~/project
nano hello.exp
在文件中输入以下内容:
#!/usr/bin/expect -f
## 设置 10 秒的超时时间
set timeout 10
## 派生 bash 进程
spawn bash
## 等待 bash 提示符
expect "$ "
## 向 bash 进程发送命令
send "echo Hello from expect\r"
## 再次等待 bash 提示符
expect "$ "
## 退出 bash 会话
send "exit\r"
## 等待进程完成
expect eof
通过按 Ctrl+O,然后 Enter 保存文件,并使用 Ctrl+X 退出 nano。
使脚本可执行:
chmod +x ~/project/hello.exp
现在运行脚本:
~/project/hello.exp
你应该看到类似于以下的输出:
spawn bash
$ echo Hello from expect
Hello from expect
$ exit
exit
理解脚本的每一行
让我解释一下脚本中每一行的作用:
#!/usr/bin/expect -f:这是一个 shebang 行,它告诉系统使用expect解释器来运行此脚本。set timeout 10:这为后续的任何expect命令设置 10 秒的超时时间。spawn bash:这启动一个新的 bash shell 进程,expect将与之交互。expect "$ ":这等待 bash 提示符出现。send "echo Hello from expect\r":这向 bash shell 发送命令。注意末尾的\r,它模拟了按下 Enter 键。expect "$ ":在命令执行后,再次等待 bash 提示符。send "exit\r":这发送 exit 命令以关闭 bash shell。expect eof:这等待派生的进程终止。
这个简单的例子演示了 expect 的核心功能。在接下来的步骤中,我们将使用这些概念来创建更实用的脚本。
使用 expect 创建一个模拟 SSH 登录脚本
在这一步中,我们将创建一个 expect 脚本,模拟 SSH 登录过程。由于我们无法在此环境中执行实际的 SSH 登录,我们将创建一个模拟脚本来演示这些原理。
理解 SSH 身份验证流程
通过 SSH 连接到远程服务器时,典型的交互包括:
- 使用
ssh username@hostname启动连接 - 接受主机密钥(如果首次连接)
- 在提示时输入你的密码
- 访问远程 shell
让我们创建一个模拟 SSH 环境来演示 expect 如何自动化这个过程。
创建一个模拟 SSH 服务器脚本
首先,让我们创建一个脚本,模拟 SSH 服务器提示输入密码:
cd ~/project
nano mock_ssh_server.sh
输入以下内容:
#!/bin/bash
echo "The authenticity of host 'mockserver' can't be established."
echo "RSA key fingerprint is SHA256:abcdefghijklmnopqrstuvwxyz123456."
echo "Are you sure you want to continue connecting (yes/no)? "
read answer
if [ "$answer" != "yes" ]; then
echo "Host key verification failed."
exit 1
fi
echo "Warning: Permanently added 'mockserver' (RSA) to the list of known hosts."
echo "Password: "
read -s password
if [ "$password" == "mockpassword" ]; then
echo "Last login: Wed Nov 1 12:00:00 2023 from 192.168.1.100"
echo "Welcome to Mock SSH Server"
echo "mockuser@mockserver:~$ "
while true; do
read -p "" command
if [ "$command" == "exit" ]; then
echo "Connection to mockserver closed."
exit 0
else
echo "Executing: $command"
echo "mockuser@mockserver:~$ "
fi
done
else
echo "Permission denied, please try again."
exit 1
fi
保存文件并使其可执行:
chmod +x ~/project/mock_ssh_server.sh
此脚本模拟:
- SSH 主机验证提示
- 密码提示
- 一个响应命令的简单 shell
创建一个 expect 脚本来自动化登录
现在,让我们创建一个 expect 脚本来自动化与我们的模拟 SSH 服务器的交互:
cd ~/project
nano ssh_login.exp
输入以下内容:
#!/usr/bin/expect -f
## 设置变量
set timeout 10
set password "mockpassword"
## 启动模拟 SSH 服务器
spawn ./mock_ssh_server.sh
## 处理主机验证提示
expect "Are you sure you want to continue connecting (yes/no)? "
send "yes\r"
## 处理密码提示
expect "Password: "
send "$password\r"
## 等待 shell 提示符
expect "mockuser@mockserver:~$ "
## 执行命令
send "ls -la\r"
expect "mockuser@mockserver:~$ "
## 退出会话
send "exit\r"
## 等待进程完成
expect eof
puts "\nSSH login automation completed successfully!"
保存文件并使其可执行:
chmod +x ~/project/ssh_login.exp
运行自动化登录脚本
现在,让我们运行我们的 expect 脚本来自动化与模拟 SSH 服务器的交互:
cd ~/project
./ssh_login.exp
你应该看到类似于以下的输出:
spawn ./mock_ssh_server.sh
The authenticity of host 'mockserver' can't be established.
RSA key fingerprint is SHA256:abcdefghijklmnopqrstuvwxyz123456.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'mockserver' (RSA) to the list of known hosts.
Password:
Last login: Wed Nov 1 12:00:00 2023 from 192.168.1.100
Welcome to Mock SSH Server
mockuser@mockserver:~$ ls -la
Executing: ls -la
mockuser@mockserver:~$ exit
Connection to mockserver closed.
SSH login automation completed successfully!
解释脚本
让我解释一下我们的 expect 脚本的每个部分的作用:
set timeout 10:为所有expect命令设置 10 秒的全局超时时间。set password "mockpassword":将密码存储在一个变量中。spawn ./mock_ssh_server.sh:启动我们的模拟 SSH 服务器脚本。expect "Are you sure you want to continue connecting (yes/no)? ":等待主机验证提示。send "yes\r":发送 "yes" 以接受主机密钥。expect "Password: ":等待密码提示。send "$password\r":发送密码。expect "mockuser@mockserver:~$ ":等待 shell 提示符。send "ls -la\r":发送一个命令来列出文件。expect "mockuser@mockserver:~$ ":再次等待 shell 提示符。send "exit\r":发送 exit 命令以关闭会话。expect eof:等待进程终止。puts "\nSSH login automation completed successfully!":打印成功消息。
这个例子演示了如何使用 expect 来自动化整个 SSH 登录过程,从接受主机密钥到在远程服务器上执行命令并安全退出。
使用 expect 处理多个提示和响应
在现实世界中,交互式程序通常会呈现多个提示,并且可能需要根据不同的条件给出不同的响应。在这一步中,我们将学习如何在 expect 脚本中处理多个提示和条件响应。
理解条件 expect 块
expect 命令可以与模式 - 动作块结构一起使用,以处理不同的可能提示:
expect {
"pattern1" { actions for pattern1 }
"pattern2" { actions for pattern2 }
timeout { actions for timeout }
eof { actions for end of file }
}
此结构允许你的脚本根据它接收到的输出以不同的方式响应。
创建一个多提示处理脚本
让我们创建一个脚本,模拟一个具有多个提示的程序:
cd ~/project
nano multi_prompt.sh
输入以下内容:
#!/bin/bash
echo "请选择一个选项:"
echo "1. 显示日期和时间"
echo "2. 列出文件"
echo "3. 显示系统信息"
echo "4. 退出"
echo -n "输入你的选择 (1-4): "
read choice
case $choice in
1)
echo "当前日期和时间:"
date
;;
2)
echo "文件列表:"
ls -la
;;
3)
echo "系统信息:"
uname -a
;;
4)
echo "正在退出程序..."
exit 0
;;
*)
echo "无效的选项。请输入 1 到 4 之间的数字。"
exit 1
;;
esac
echo "你想要继续吗? (yes/no): "
read answer
if [ "$answer" == "yes" ]; then
echo "继续..."
echo "操作成功完成。"
else
echo "正在退出程序..."
fi
保存文件并使其可执行:
chmod +x ~/project/multi_prompt.sh
现在,让我们创建一个 expect 脚本来与此程序交互并处理所有可能的提示:
cd ~/project
nano handle_prompts.exp
输入以下内容:
#!/usr/bin/expect -f
## 设置超时时间
set timeout 10
## 启动多提示程序
spawn ./multi_prompt.sh
## 等待选择提示
expect "输入你的选择 (1-4): "
## 生成一个随机选择 (1-3)
set choice [expr {int(rand() * 3) + 1}]
send "$choice\r"
## 根据选择处理结果
switch $choice {
1 {
expect "当前日期和时间:"
expect "你想要继续吗? (yes/no): "
}
2 {
expect "文件列表:"
expect "你想要继续吗? (yes/no): "
}
3 {
expect "系统信息:"
expect "你想要继续吗? (yes/no): "
}
}
## 处理继续提示
expect {
"你想要继续吗? (yes/no): " {
## 70% 的几率说 yes,30% 的几率说 no
if {rand() < 0.7} {
send "yes\r"
expect "操作成功完成。"
} else {
send "no\r"
expect "正在退出程序..."
}
}
timeout {
puts "等待继续提示超时"
exit 1
}
}
## 等待程序完成
expect eof
puts "\n多提示处理成功完成!"
保存文件并使其可执行:
chmod +x ~/project/handle_prompts.exp
运行多提示处理脚本
现在,让我们运行我们的 expect 脚本来与多提示程序交互:
cd ~/project
./handle_prompts.exp
每次运行此脚本时,它将随机选择一个选项,并随机决定是继续还是退出。这是一个示例输出:
spawn ./multi_prompt.sh
请选择一个选项:
1. 显示日期和时间
2. 列出文件
3. 显示系统信息
4. 退出
输入你的选择 (1-4): 2
文件列表:
total 20
drwxr-xr-x 2 labex labex 4096 Nov 1 10:00 .
drwxr-xr-x 4 labex labex 4096 Nov 1 10:00 ..
-rwxr-xr-x 1 labex labex 345 Nov 1 10:00 handle_prompts.exp
-rwxr-xr-x 1 labex labex 578 Nov 1 10:00 multi_prompt.sh
-rwxr-xr-x 1 labex labex 221 Nov 1 10:00 ssh_login.exp
你想要继续吗? (yes/no): yes
继续...
操作成功完成。
多提示处理成功完成!
创建一个更高级的 expect 脚本
现在让我们创建一个更高级的脚本,它可以处理意外的提示和错误:
cd ~/project
nano advanced_expect.exp
输入以下内容:
#!/usr/bin/expect -f
## 设置超时时间
set timeout 10
## 定义变量
set program "./multi_prompt.sh"
set max_retries 3
set retry_count 0
## 定义一个用于处理错误的程序
proc handle_error {} {
global retry_count max_retries program
incr retry_count
if {$retry_count < $max_retries} {
puts "\n正在重试... 尝试 $retry_count 次,共 $max_retries 次"
## 再次启动程序
spawn $program
return 1
} else {
puts "\n已达到最大重试次数。正在退出。"
exit 1
}
}
## 启动程序
spawn $program
## 主交互循环
while {$retry_count < $max_retries} {
expect {
"输入你的选择 (1-4): " {
send "1\r" ## 始终选择选项 1 以获得确定性行为
}
"无效的选项" {
puts "\n收到无效选项消息。"
if {[handle_error]} continue
}
"当前日期和时间:" {
## 成功获取日期输出
}
"你想要继续吗? (yes/no): " {
send "yes\r"
}
"操作成功完成。" {
puts "\n高级 expect 脚本成功完成!"
break
}
timeout {
puts "\n等待提示时发生超时。"
if {[handle_error]} continue
}
eof {
puts "\n意外的文件结尾。"
if {[handle_error]} continue
}
}
}
## 等待程序完成
expect eof
保存文件并使其可执行:
chmod +x ~/project/advanced_expect.exp
运行高级脚本:
cd ~/project
./advanced_expect.exp
示例输出:
spawn ./multi_prompt.sh
请选择一个选项:
1. 显示日期和时间
2. 列出文件
3. 显示系统信息
4. 退出
输入你的选择 (1-4): 1
当前日期和时间:
Wed Nov 1 10:00:00 UTC 2023
你想要继续吗? (yes/no): yes
继续...
操作成功完成。
高级 expect 脚本成功完成!
理解高级脚本
这个高级脚本演示了几种重要的 expect 技术:
- 错误处理:它使用重试机制来处理错误或意外的响应。
- 过程(Procedures):它定义了一个名为
handle_error的自定义过程,用于可重用的错误处理。 - 控制流:它使用 while 循环来维持交互,直到成功或达到最大重试次数。
- 多个 expect 模式:它处理多个不同的模式,并为每个模式采取适当的动作。
- 模式顺序:
expect块中模式的顺序很重要——更具体的模式应该放在更一般的模式之前。
这些技术可以应用于自动化复杂的交互式程序,其中流程可能会发生变化或可能发生错误。
为常见任务创建实用的 expect 脚本
在这一步中,我们将为系统管理员经常需要自动化的常见任务创建实用的 expect 脚本。我们将重点关注文件操作、用户交互和系统监控。
使用 expect 自动化文件传输
让我们创建一个 expect 脚本,使用 scp 命令自动化文件的传输。由于我们无法在此环境中执行实际的文件传输,我们将模拟它:
cd ~/project
nano file_transfer.sh
输入以下内容以模拟类似 SCP 的文件传输:
#!/bin/bash
echo "scp 文件传输模拟"
echo "源文件: $1"
echo "目标: $2"
echo "密码: "
read -s password
if [ "$password" == "transfer123" ]; then
echo "正在传输文件..."
echo "0%"
sleep 1
echo "25%"
sleep 1
echo "50%"
sleep 1
echo "75%"
sleep 1
echo "100%"
echo "文件传输成功完成。"
else
echo "身份验证失败。"
exit 1
fi
保存文件并使其可执行:
chmod +x ~/project/file_transfer.sh
现在,让我们创建一个 expect 脚本来自动化此文件传输:
cd ~/project
nano file_transfer.exp
输入以下内容:
#!/usr/bin/expect -f
## 设置变量
set timeout 10
set source_file "local_file.txt"
set destination "user@remote:/path/to/destination/"
set password "transfer123"
## 创建一个虚拟源文件
spawn bash -c "echo '这是一个测试文件' > $source_file"
expect eof
## 启动文件传输模拟
spawn ./file_transfer.sh $source_file $destination
## 处理密码提示
expect "密码: "
send "$password\r"
## 监控传输进度
expect "0%"
puts "传输开始..."
expect "25%"
puts "传输 1/4 完成..."
expect "50%"
puts "传输 1/2 完成..."
expect "75%"
puts "传输 3/4 完成..."
expect "100%"
puts "传输即将完成..."
expect "文件传输成功完成。"
puts "文件传输自动化完成!"
## 清理虚拟文件
spawn bash -c "rm $source_file"
expect eof
保存文件并使其可执行:
chmod +x ~/project/file_transfer.exp
运行文件传输自动化脚本:
cd ~/project
./file_transfer.exp
示例输出:
spawn bash -c echo '这是一个测试文件' > local_file.txt
spawn ./file_transfer.sh local_file.txt user@remote:/path/to/destination/
scp 文件传输模拟
源文件: local_file.txt
目标: user@remote:/path/to/destination/
密码:
正在传输文件...
0%
传输开始...
25%
传输 1/4 完成...
50%
传输 1/2 完成...
75%
传输 3/4 完成...
100%
传输即将完成...
文件传输成功完成。
文件传输自动化完成!
spawn bash -c rm local_file.txt
使用 expect 自动化用户创建
现在,让我们创建一个 expect 脚本来自动化用户创建。同样,我们将模拟这个过程:
cd ~/project
nano create_user.sh
输入以下内容:
#!/bin/bash
echo "用户创建实用程序"
echo "请输入新用户名: "
read username
echo "请输入 $username 的密码: "
read -s password
echo "请确认密码: "
read -s password_confirm
if [ "$password" != "$password_confirm" ]; then
echo "错误: 密码不匹配。"
exit 1
fi
echo "正在创建用户 $username..."
echo "用户 $username 创建成功。"
echo "你想要将此用户添加到 sudo 组吗? (yes/no): "
read sudo_choice
if [ "$sudo_choice" == "yes" ]; then
echo "正在将 $username 添加到 sudo 组..."
echo "用户 $username 已添加到 sudo 组。"
fi
echo "用户设置完成。"
保存文件并使其可执行:
chmod +x ~/project/create_user.sh
现在,让我们创建一个 expect 脚本来自动化用户创建:
cd ~/project
nano create_user.exp
输入以下内容:
#!/usr/bin/expect -f
## 设置变量
set timeout 10
set username "testuser"
set password "P@ssw0rd123"
set add_sudo "yes"
## 启动用户创建实用程序
spawn ./create_user.sh
## 处理用户名提示
expect "请输入新用户名: "
send "$username\r"
## 处理密码提示
expect "请输入 $username 的密码: "
send "$password\r"
## 处理密码确认提示
expect "请确认密码: "
send "$password\r"
## 等待用户创建确认
expect "用户 $username 创建成功。"
## 处理 sudo 提示
expect "你想要将此用户添加到 sudo 组吗? (yes/no): "
send "$add_sudo\r"
## 如果我们选择添加到 sudo,等待确认
if {$add_sudo == "yes"} {
expect "用户 $username 已添加到 sudo 组。"
}
## 等待完成
expect "用户设置完成。"
puts "\n用户创建自动化成功完成!"
保存文件并使其可执行:
chmod +x ~/project/create_user.exp
运行用户创建自动化脚本:
cd ~/project
./create_user.exp
示例输出:
spawn ./create_user.sh
用户创建实用程序
请输入新用户名:
testuser
请输入 testuser 的密码:
请确认密码:
正在创建用户 testuser...
用户 testuser 创建成功。
你想要将此用户添加到 sudo 组吗? (yes/no):
yes
正在将 testuser 添加到 sudo 组...
用户 testuser 已添加到 sudo 组。
用户设置完成。
用户创建自动化成功完成!
理解实用的 expect 脚本
我们创建的实用脚本演示了几个用于实际自动化任务的重要概念:
- 顺序交互:两个脚本都遵循已定义的提示和响应顺序。
- 进度监控:文件传输脚本监控进度并提供用户友好的更新。
- 条件逻辑:用户创建脚本使用条件逻辑来处理 sudo 选项。
- 环境设置和清理:文件传输脚本创建和清理测试文件。
这些技术可以应用于自动化许多常见的系统管理任务,例如:
- 远程备份
- 软件安装
- 系统配置
- 批量操作
通过掌握 expect,你可以自动化复杂的交互式流程,否则这些流程将需要手动干预,从而节省时间并减少人为错误的潜在可能性。
总结
在这个实验中,你已经学习了如何在 Linux 中使用 expect 命令来自动化交互式命令行应用程序。你获得了以下方面的实践经验:
- 安装并理解
expect命令的基本语法 - 创建脚本以自动化 SSH 登录并处理各种身份验证提示
- 在
expect脚本中处理多个提示和响应 - 为常见的系统管理任务创建实用的自动化脚本
expect 命令是系统管理员和需要自动化交互式流程的开发人员的强大工具。通过使用 expect,你可以消除在重复性任务中进行手动干预的需要,从而节省时间并降低人为错误的风险。
本实验的一些关键要点:
expect命令使用模式 - 动作模型与程序交互- 通过处理各种可能的提示和错误条件,可以使脚本更加健壮
- 可以使用条件逻辑和自定义过程来自动化复杂的交互
- 实际的自动化可以显著提高常见系统任务的效率
通过你在本实验中学习的技能,你现在可以为各种交互式命令行应用程序创建你自己的自动化脚本。



