Linux expect 命令实战:附带实用示例

LinuxBeginner
立即练习

介绍

在这个实验中,你将学习如何使用 Linux 的 expect 命令来自动化交互式命令行应用程序。expect 命令是一个强大的自动化工具,它允许脚本与需要用户输入的程序进行交互,例如 SSH、FTP 以及其他交互式程序。

完成这个实验后,你将能够:

  • 了解 expect 命令的目的和基本语法
  • 创建脚本来自动化 SSH 登录
  • 在你的 expect 脚本中处理各种提示和响应

expect 命令可以显著减少重复任务的手动干预,使系统管理和日常任务更有效率。你将从安装和探索 expect 的基本语法开始,然后逐步创建脚本来自动化 SSH 登录并处理各种交互式提示。

Linux 命令速查表

理解 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 脚本的基本结构包括以下命令:

  1. spawn:启动一个要交互的进程
  2. expect:等待来自派生进程的特定输出
  3. send:向派生进程发送输入
  4. 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 连接到远程服务器时,典型的交互包括:

  1. 使用 ssh username@hostname 启动连接
  2. 接受主机密钥(如果首次连接)
  3. 在提示时输入你的密码
  4. 访问远程 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 脚本的每个部分的作用:

  1. set timeout 10:为所有 expect 命令设置 10 秒的全局超时时间。
  2. set password "mockpassword":将密码存储在一个变量中。
  3. spawn ./mock_ssh_server.sh:启动我们的模拟 SSH 服务器脚本。
  4. expect "Are you sure you want to continue connecting (yes/no)? ":等待主机验证提示。
  5. send "yes\r":发送 "yes" 以接受主机密钥。
  6. expect "Password: ":等待密码提示。
  7. send "$password\r":发送密码。
  8. expect "mockuser@mockserver:~$ ":等待 shell 提示符。
  9. send "ls -la\r":发送一个命令来列出文件。
  10. expect "mockuser@mockserver:~$ ":再次等待 shell 提示符。
  11. send "exit\r":发送 exit 命令以关闭会话。
  12. expect eof:等待进程终止。
  13. 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 技术:

  1. 错误处理:它使用重试机制来处理错误或意外的响应。
  2. 过程(Procedures):它定义了一个名为 handle_error 的自定义过程,用于可重用的错误处理。
  3. 控制流:它使用 while 循环来维持交互,直到成功或达到最大重试次数。
  4. 多个 expect 模式:它处理多个不同的模式,并为每个模式采取适当的动作。
  5. 模式顺序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 脚本

我们创建的实用脚本演示了几个用于实际自动化任务的重要概念:

  1. 顺序交互:两个脚本都遵循已定义的提示和响应顺序。
  2. 进度监控:文件传输脚本监控进度并提供用户友好的更新。
  3. 条件逻辑:用户创建脚本使用条件逻辑来处理 sudo 选项。
  4. 环境设置和清理:文件传输脚本创建和清理测试文件。

这些技术可以应用于自动化许多常见的系统管理任务,例如:

  • 远程备份
  • 软件安装
  • 系统配置
  • 批量操作

通过掌握 expect,你可以自动化复杂的交互式流程,否则这些流程将需要手动干预,从而节省时间并减少人为错误的潜在可能性。

总结

在这个实验中,你已经学习了如何在 Linux 中使用 expect 命令来自动化交互式命令行应用程序。你获得了以下方面的实践经验:

  • 安装并理解 expect 命令的基本语法
  • 创建脚本以自动化 SSH 登录并处理各种身份验证提示
  • expect 脚本中处理多个提示和响应
  • 为常见的系统管理任务创建实用的自动化脚本

expect 命令是系统管理员和需要自动化交互式流程的开发人员的强大工具。通过使用 expect,你可以消除在重复性任务中进行手动干预的需要,从而节省时间并降低人为错误的风险。

本实验的一些关键要点:

  • expect 命令使用模式 - 动作模型与程序交互
  • 通过处理各种可能的提示和错误条件,可以使脚本更加健壮
  • 可以使用条件逻辑和自定义过程来自动化复杂的交互
  • 实际的自动化可以显著提高常见系统任务的效率

通过你在本实验中学习的技能,你现在可以为各种交互式命令行应用程序创建你自己的自动化脚本。

Linux 命令速查表