shell脚本——expect的综合使用(包含应用案例)

这篇博客介绍了如何使用Linux自动化工具Expect进行交互式任务,包括磁盘自动分区脚本、通过SSH远程登录执行命令、FTP下载文件以及在多台服务器上创建用户。通过Expect,可以实现SSH登录过程的自动化,避免手动输入密码,提高运维效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Expect概述

  • Expect是建立在tcl基础上的一个工具,Expect 是用来进行自动化控制和测试的工具。主要解决shell脚本中不可交互的问题。对于大规模的linux 运维很有帮助
  • 在linux运维和开发中,我们经常需要远程登录服务器进行操作,登录的过程是一个交互的过程,可能会需要输入yes/no password等信息。为了模拟这种输入,可以使用Expect脚本

使用expect之前需要先安装
yum –y intall expect

基本命令

  • send:向进程发送字符串,用于模拟用户的输入
    该命令不能自动回车换行,一般要加 \r(回车)

  • expect:expect的一个内部命令,判断上次输出结果里是否包含指定的字符串,如果有则立即返回,否则就等待超时时间后返回。只能捕捉由spawn启动的进程的输出

  • spawn:启动进程,并跟踪后续交互信息

  • interact:执行完成后保持交互状态,把控制权交给控制台

  • Timeout:指定超时时间,过期则继续执行后续指令

  • exp_continue:允许expect继续向下执行指令

  • send_user:回显命令,相当于echo

  • $argv 参数数组:Expect脚本可以接受从bash传递的参数.可以使用[lindex $argv n]获得,n从0开始,分别表示第一个,第二个,第三个…参数

Expect脚本必须以interact或expect eof结束,执行自动化任务通常expect eof就够了

应用案例

磁盘自动分区脚本

#!/bin/bash

#安装expect
rpm -q expect &>/dev/null
if [ $? -ne 0 ];then
	yum install -y expect >/dev/null
	echo "expect安装成功"
fi

#设置挂载硬盘的参数
fdisk -l |egrep '^Disk /dev/sd.*'
read -p "请输入需要挂载得硬盘路径: " disk_new
partitions="${disk_new}1"

#expect交互脚本
/usr/bin/expect << EOF
set timeout 30
spawn bash -c "fdisk $disk_new"

expect "Command*" {send "n\r"} 
expect "*p*" {send "p\r"}
expect "*1*" {send "\r"}
expect "First*" {send "\r"}
expect "Last*" {send "\r"}
expect "*help*" {send "wq\r"} 

expect eof
interact	
EOF

#格式化磁盘
mkfs -t xfs $partitions


#挂载
read -p "输入需要挂载得路径:" target_dir
if [ ! -d $target_dir ]
then
	mkdir -p $target_dir
fi
echo "$partitions            $target_dir                xfs       defaults              0 0" >> /etc/fstab

#重载fstab配置
mount -a
df -Th

远程ssh另一台主机

需求1:A远程登录到server上什么都不做

#!/usr/bin/expect
# 开启一个程序
spawn ssh root@10.1.1.1
# 捕获相关内容
expect {
        "(yes/no)?" { send "yes\r";exp_continue }
        "password:" { send "123456\r" }
}
interact  # interact 这个写在最后代表交互,如果不写执行完动作就会退出

脚本执行方式:
# ./expect.sh
# /shell/expect.sh
# expect -f expect.sh

1)定义变量
#!/usr/bin/expect
set ip 192.168.188.188
set pass 123
set timeout 5
spawn ssh root@$ip
expect {
    "yes/no" { send "yes\r";exp_continue }
    "password:" { send "$pass\r" }
}
interact


2)使用位置参数
#!/usr/bin/expect
set ip [ lindex $argv 0 ]
set pass [ lindex $argv 1 ]
set timeout 5
spawn ssh root@$ip
expect {
    "yes/no" { send "yes\r";exp_continue }
    "password:" { send "$pass\r" }
}
interact

需求2:A远程登录到server上操作

#!/usr/bin/expect
set ip 192.168.188.188
set pass 123
set timeout 5
spawn ssh root@$ip
expect {
    "yes/no" { send "yes\r";exp_continue }
    "password:" { send "$pass\r" }
}

expect "#"
send "rm -rf /tmp/*\r"		# 删除临时文件
send "touch /tmp/file{1..3}\r"	# 创建临时文件
send "date\r"
send "exit\r"
expect eof

需求3:shell脚本和expect结合使用,在多台服务器上创建1个用户

[root@server shell04]# cat ip.txt 
192.168.188.188 123
192.168.188.186 123


1. 循环  useradd username
2. 登录远程主机——>ssh——>从ip.txt文件里获取IP和密码分别赋值给两个变量
3. 使用expect程序来解决交互问题


#!/bin/bash
# 循环在指定的服务器上创建用户和文件
while read ip pass
do
    /usr/bin/expect <<-EOF &>/dev/null
    spawn ssh root@$ip
    expect {
    "yes/no" { send "yes\r";exp_continue }
    "password:" { send "$pass\r" }
    }
    expect "#" { send "useradd maomao;rm -rf /tmp/*;exit\r" }
    expect eof
    EOF
echo "$ip服务器用户创建完毕"
done < ip.txt

自动连接ftp下载文件

#!/bin/bash
#自动连接ftp服务器然后下载pub下面的test.txt
#by stanZ 2021-1-27
if [ $# -eq 0 ];then
        echo "用法:`basename $0` ftp服务器ip地址"
        exit 1
fi
pgrep firewalld &>/dev/null
if [ $? -eq 0 ];then
        systemctl stop firewalld &>/dev/null
fi

if [[ $(getenforce) == Enforcing ]];then
        setenforce 0
        sed -ri '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config &>/dev/null
fi

rpm -q expect &>/dev/null
if [ $? -ne 0 ];then
        [ $UID -eq 0 ] && yum install -y expect &>/dev/null || echo "你没有权限安装expect"
fi

{
        /usr/bin/expect <<-EOF 
        set timeout 5   
        spawn ftp $1
        expect  {
                "):" { send "ftp\r";exp_continue }
                "Password:" { send "\r" }
        }
        expect "ftp>" 
        send "cd pub\r" 
        send "get test.txt\r" 
        send "bye\r" 
        expect eof
        EOF
}&>/dev/null
wait
echo "test.txt文件下载完毕"
                               

综合案例

写一个脚本,将跳板机上yunwei用户的公钥推送到局域网内可以ping通的所有机器上
说明:主机和密码文件已经提供

192.168.188.188:123

192.168.188.186:123

步骤分析

  • 跳板机上的yunwei用户生成秘钥对
    判断账号是否存在 (id yunwei)
    判断该用户是否有密钥对文件 [ -f xxx ]
  • 判断expect程序是否安装
  • 判断局域网内主机是否ping通
    循环判断 for while
    循环体do…done ping 主机 如果ping通 调用expect程序自动应答推送公钥
  • 测试验证是否免密登录成功
  • 检查服务器上ssh服务端口号
  • 把公钥推送成功的主机的信息保存到文件
  • 关闭防火墙和selinux
  • 日志记录
  • 推送公钥需要自动应答expect

这里要实现功能 要切换成yunwei账户 必须写两个脚本文件
第一个脚本由root执行
第二个脚本由运维执行

#!/bin/bash
#ssh自动上传公钥,并且能远程控制
#by stanZ      2021-1-11
# 判断防火墙是否关闭
pgrep firewalld &>/dev/null
if [ $? -eq 0 ];then
        systemctl stop firewalld &>/dev/null	# 关闭防火墙
fi

# 关闭selinux安全中心
if [[ $(getenforce) = Enforcing ]];then
        setenforce 0
        sed -ri '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config &>/dev/null
fi


{
id yunwei &>/dev/null	# 判断yunwei账户是否存在 不存在则创建
[ $? -ne 0 ] && useradd yunwei && echo 123|passwd --stdin yunwei
# 给yunwei账户sudo授权 取消注释
sed -ri 's/^#(.*required.*)/\1/' /etc/pam.d/su
sed -ri 's/^#(.*NOPASSWD)/\1/' /etc/sudoers
# 加入wheel组拥有权限
gpasswd -a yunwei wheel
} >/dev/null

# 判断expect是否安装
rpm -q expect &>/dev/null
if [ $? -ne 0 ];then
        yum install -y expect >/dev/null
        echo "expect安装成功"
fi

su - yunwei

补充:

[ ! -f /root/.ssh/id_rsa.pub ] && ssh-keygen -P '' -f /root/.ssh/id_rsa

ssh-keygen -P'' 	设定密码为空

 -f /root/.ssh/id_rsa 指定密钥位置
 
这样就不需要交互操作
#!/bin/bash
#ssh自动上传公钥,并且能远程控制
#by  stanZ     2021-1-11
# 判断私钥是否创建 如果没创建则使用免交互的命令
if [ ! -f /home/yunwei/.ssh/id_rsa ];then
        ssh-keygen -P '' -f ~/.ssh/id_rsa &>/dev/null
fi

while read ip pass
do
{
        ping -c1 $ip 
        if [ $? -eq 0 ];then
                echo $ip >> /home/yunwei/ip_up.txt
                /usr/bin/expect <<-EOF
                set timeout 10
                spawn ssh-copy-id root@$ip
                expect  "(yes/no)" { send "yes\r";exp_continue }
                expect  "password:" { send "$pass\r" }
                        expect eof
                EOF
                else
                        echo $ip >> /home/yunwei/ip_down.txt
                fi
}&>/dev/null
done < /home/ip.txt
wait
echo "公钥推送完毕,准备测试"
remote_ip=`head -1 /home/yunwei/ip_up.txt`
ssh root@$remote_ip hostname &>/dev/null
test $? -eq 0 && echo "公钥成功推送,并且能远程连接"

### 配置 Qt 项目以使用 Google Test 为了使 Qt 项目能够利用 Google Test 执行单元测试,需遵循一系列特定的操作指南。 #### 安装依赖项 对于 Linux 用户而言,在基于 Debian 的发行版上可以通过包管理工具安装必要的软件包。这通常涉及获取 `libgtest-dev` 和其他可能必需的支持库[^2]: ```bash sudo apt-get update && sudo apt-get install libgtest-dev cmake ``` #### 构建 Google Test 库 下载的源码需要被构建以便生成静态链接库供后续使用。进入 `/usr/src/gtest/` 路径下创建并导航至一个名为 build 的目录内运行 CMake 命令完成此过程;之后再调用 make 来编译这些文件: ```bash cd /usr/src/gtest/ mkdir build && cd $_ cmake .. make ``` 接着把新建立好的 .a 文件复制到系统的标准库路径中去,从而让链接阶段可以顺利找到它们: ```bash sudo cp *.a /usr/lib ``` #### 修改 `.pro` 文件 为了让 qmake 工具识别谷歌测试框架的存在及其头文件的位置,应该编辑项目的 pro 文件加入相应的 INCLUDEPATH 及 LIBS 参数指向 gtest 头文件夹和刚才放置好共享对象(.so)/静态档案(.a)之处[^1]。 ```qmake QT += core gui testlib CONFIG += c++17 warn_on unittest INCLUDEPATH += /usr/include/gtest \ /usr/include/gmock LIBS += -lgtest -pthread ``` #### 创建测试类与函数 定义继承自 ::testing::Test 类的新类别用于封装待测逻辑,并实现 SetUp() 方法初始化资源或 TearDown() 清理工作环境。随后编写具体的 TEST_F 或者 INSTANTIATE_TEST_SUITE_P 测试案例来验证目标行为是否符合预期。 ```cpp #include <gtest/gtest.h> class MyFirstQtTestClass : public testing::Test { protected: void SetUp() override { /* setup code */ } void TearDown() override { /* cleanup code */ } public: // member variables and methods used by tests go here... }; TEST_F(MyFirstQtTestClass, ExampleTestCase) { EXPECT_EQ(1 + 1, 2); } ``` #### 设置自动执行测试本 最后一步是在 CI/CD 环境里安排定期触发上述所有步骤的过程——即从克隆仓库开始直到最终报告结果为止的一整套动作序列。也可以考虑将整个流程打包成 shell script 方便重复操作。 ```bash #!/bin/bash set -e # Compile the project with debug symbols enabled. qmake CONFIG+=debug . make clean all # Run unit tests using GTest executable generated during compilation. ./path/to/executable --gtest_output=xml:test_results.xml ```
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值