expect基本使用
使用expect创建脚本的方法
1.定义脚本执行的shell
#!/usr/bin/expect
这里定义的是expect可执行文件的链接路径(或真实路径),功能类似于bash等shell功能。
2.set timeout 30
设置超时时间,单位是秒,如果设置为timeout -1意为永不超时。
3.spawn
spawn是进入expect环境后才能执行的内部命令,不能直接在默认的shell环境中进行执行
主要功能:传递交互命令
4.expect
这里的expect同样是expect的内部命令
主要功能:判断输出结果是否包含某项字符串,没有则立即返回,否则就等待一段时间后返回,等待时间通过timeout进行设置。
5.send
执行交互动作,将交互要执行的动作进行输入给交互指令
命令字符串结尾要加上"r",如果出现异常等待的状态可以进行核查
6.interact
执行完后保持交互状态,把控制权交给控制台
如果不加这一项,交互完成后自动退出
7.exp_continue
继续执行接下来的交互操作
8.$argv
expect脚本可以接收从bash传递过来的参数,可以使用[index $atgv n]获得,n从0开始,分别表示第一个,第二个,第三个......参数
案例
案例1:实现ssh远程登录
#!/usr/bin/expect
# 开启一个程序
spawn ssh root@10.0.0.12
expect {
"yes/no" {
send "yes\r"
exp_continue
}
"password:" {
send "123456\r"
}
}
interact
[root@openEuler-14 data]# ./ssh-v0.sh
spawn ssh root@10.0.0.12
root@10.0.0.12's password:
Activate the web console with: systemctl enable --now cockpit.socket
Last login: Sun Apr 27 21:42:07 2025 from 10.0.0.14
[root@Rocky-12 ~]# exit
注销
Connection to 10.0.0.12 closed.
案例2:免密码登录其他主机
[root@openEuler-14 data]# cat ssh.exp
#!/usr/bin/expect
set ipaddress "10.0.0.12"
set passwd "123456"
set timeout 30
spawn ssh root@$ipaddress
expect {
"yes/no" {
send "yes\r"
exp_continue
}
"password:" {
send "$passwd\r"
}
}
interact
chmod +x ssh.exp
./ssh.exp
执行验证:
[root@openEuler-14 data]# ./ssh.exp
spawn ssh root@10.0.0.12
root@10.0.0.12's password:
Activate the web console with: systemctl enable --now cockpit.socket
Last login: Tue Apr 1 16:28:20 2025 from 10.0.0.1
[root@Rocky-12 ~]# exit
注销
Connection to 10.0.0.12 closed.
案例3:通过调用bash的位置参数实现ssh远程登录
#!/usr/bin/expect
set ipaddress [ lindex $argv 0 ]
set user [ lindex $argv 1 ]
set passwd [ lindex $argv 2 ]
set timeout 30
spawn ssh $user@$ipaddress
expect {
"yes/no" {
send "yes\r"
exp_continue
}
"password:" {
send "$passwd\r"
}
}
interact
[root@openEuler-14 data]# ./ssh.exp 10.0.0.12 root 123456
spawn ssh root@10.0.0.12
root@10.0.0.12's password:
Activate the web console with: systemctl enable --now cockpit.socket
Last login: Sun Apr 27 20:57:37 2025 from 10.0.0.14
案例4:A远程登录到server上操作
#!/usr/bin/expect
set ipaddress "10.0.0.12"
set passwd "123456"
set timeout 30
spawn ssh root@$ipaddress
expect {
"yes/no" {
send "yes\r"
exp_continue
}
"password:" {
send "$passwd\r"
}
}
#interact
expect "#"
send "hostname\r"
send "useradd test01\r"
send "pwd\r"
send "exit\r"
expect eof
测试效果
[root@openEuler-14 data]# ./ssh-v3.sh
spawn ssh root@10.0.0.12
root@10.0.0.12's password:
Activate the web console with: systemctl enable --now cockpit.socket
Last login: Sun Apr 27 21:54:27 2025 from 10.0.0.14
[root@Rocky-12 ~]# hostname
Rocky-12
[root@Rocky-12 ~]# useradd test01
[root@Rocky-12 ~]# pwd
/root
[root@Rocky-12 ~]# exit
注销
Connection to 10.0.0.12 closed.
[root@openEuler-14 data]#
案例5:shell脚本和expect结合使用,在多台服务器上创建1个用户
[root@openEuler-14 data]# cat ip.txt
10.0.0.12 123456
10.0.0.13 123456
1.循环
2.登录远程主机-->ssh-->从ip.txt文件里获取IP和密码分别赋值给两个变量
3.使用expect程序来解决交互问题
[root@openEuler-14 data]# cat a.sh
#!/bin/bash
while read ip username;do
echo $ip,$username
done < ip.txt
[root@openEuler-14 data]# bash a.sh
10.0.0.12,123456
10.0.0.13,123456
脚本
#!/bin/bash
# 循环在指定的服务器上创建用户和文件
while read ipaddress passwd
do
/usr/bin/expect <<-END &>/dev/null
spawn ssh root@$ipaddress
expect {
"yes/no" {
send "yes\r"
exp_continue
}
"password:" {
send "$passwd\r"
}
}
expect "#" {
send "useradd zhangsan;rm -rf /tmp/*;exit\r"
}
expect eof
END
done < ip.txt
执行程序
[root@openEuler-14 data]# ./ssh-v4.sh
[root@openEuler-14 data]#
测试结果
root@ubuntu-13:~# ls /tmp
10.txt 1.txt 2.txt 3.txt 4.txt 5.txt 6.txt 7.txt 8.txt 9.txt
root@ubuntu-13:~# id zhangsan
id: "zhangsan": 无此用户
root@ubuntu-13:~# ls /tmp
root@ubuntu-13:~# id zhangsan
uid=1001(zhangsan) gid=1001(zhangsan) 组=1001(zhangsan)
[root@Rocky-12 ~]# ls /tmp
10.txt 1.txt 2.txt 3.txt 4.txt 5.txt 6.txt 7.txt 8.txt 9.txt
[root@Rocky-12 ~]# id zhangsan
id: “zhangsan”:无此用户
[root@Rocky-12 ~]# ls /tmp
[root@Rocky-12 ~]# id zhangsan
用户id=1002(zhangsan) 组id=1002(zhangsan) 组=1002(zhangsan)
综合案例
实战案例1:shell批量推送公钥脚本
写一个脚本,将跳板机上yunwei用户的公钥推送到局域网内可以ping通的所有机器上
说明:主机和密码文件已经提供
10.0.0.10:123456
10.0.0.11:123456
10.0.0.12:123456
10.0.0.13:123456
[root@openEuler-14 data]# cat ip.txt | tr ':' ' ' > ip.bak
[root@openEuler-14 data]# tr ':' ' ' < ip.txt > ip.bak
[root@openEuler-14 data]# cat ip.bak
10.0.0.10 123456
10.0.0.11 123456
10.0.0.12 123456
10.0.0.13 123456
[root@openEuler-14 data]# tr ':' ' ' < ip.txt | tee ip.bak
10.0.0.10 123456
10.0.0.11 123456
10.0.0.12 123456
10.0.0.13 123456
[root@openEuler-14 data]# tr ':' ' ' < ip.txt | while read ip pass;do echo $ip:$pass;done
10.0.0.10 123456
10.0.0.11 123456
10.0.0.12:123456
10.0.0.13:123456
案例分析:
- 关闭防火墙和selinux
- 判断ssh服务是否开启(默认ok)
- ==循环判断给定密码文件里的哪些IP是可以ping通的== ip pass
- ==判断IP是否可以ping通--->$?-->流程控制语句==
- ==密码文件里获取主机的IP和密码保存变量==ip pass
- ==判断公钥是否存在-->不存在创建它==
- ==ssh-copy-id将跳板机上的yunwei用户的公钥推送到远程主机-->expect解决交换==
- 将ping通的主机IP单独保存到一个文件
- 测试验证
代码拆分:
1.获取IP并且判断是否可以ping通
1) 主机密码文件ip.txt
10.0.0.10:123456
10.0.0.11:123456
10.0.0.12:123456
10.0.0.13:123456
2) 循环判断主机是否ping通
tr ':' ' ' < ip.txt | while read ip pass
do
ping -c1 $ip &>/dev/null
if [ $? -eq 0 ];then
判断公钥是否存在
推送公钥
fi
done
2.判断yunwei用户的公钥是否存在
[ ! -f /hmoe/yunwei/.ssh/id_rsa ] && ssh-keygen -P '' -f /root/.ssh/id_rsa
3.非交互推送公钥
/usr/bin/expect <<-END &>/dev/null
spawn ssh-copy-id root@$ip
expect {
"yes/no" {
send "yes\r"
exp_continue
}
"password:" {
send "$pass\r"
}
}
expect eof
END
最终实现:
环境:
jumper-server 有yunwei用户
1.在跳板机上创建yunwei用户,并且生成一对秘钥
2.检测当前局域网中哪些ip是能ping通,哪些是不能ping通 循环语句并发的去检查
3.在脚本中所有的交互动作需要用到expect实现
yunwei用户sudo授权:
visudo
## Allow root to run any commands anywhere
root ALL=(ALL) ALL
yunwei ALL=(root) NOPASSWD:ALL,!/sbin/shutdown,!/sbin/init,!/bin/rm -rf /
解释说明:
1)第一个字段yunwei指定的是用户:可以是用户名,也可以是别名。每个用户设置一行,多个用户设置多行,也可以将多个用户设置成一个别名后再进行设置。
2)第二个字段ALL指定的是用户所在的主机:可以是ip,也可以是主机名,表示该sudo设置只在该主机上生效,ALL表示在所有主机上都生效!限制的一般都是本机,也就是限制使用这个文件的主机;一般都指定为”ALL"表示所有的主机,不管文件拷到那里都可以用。比如:10.1.1.1=...则表示只在当前主机生效。
3)第三个字段(root)括号里指定的也是用户:指定以什么用户身份执行sudo,即使用sudo后可以享有所有root账号下的权限。如果要排除个别用户,可以在括号内设置,比如ALL=(ALL,!oracle,!pos)。
4)第四个字段ALL指定的是执行的命令:即使用sudo后可以执行所有的命令。除了关机和删除根内容以外;也可以设置别名。NOPASSWD:ALL表示使用sudo的不需要输入密码。
5)也可以授权给一个用户组
%admin ALL=(ALL) ALL表示admin组里的所有成员可以在任何主机上以任何用户身份执行任何命令
#!/bin/bash
# push publickey to app-servers
# 将局域网内可以ping通的主机ip保存到一个文件
> ip_up.txt
tr ':' ' ' < ip.txt | while read ip pass
do
{
ping -c1 $ip &>/dev/null
[ $? -eq 0 ] && echo "$ip $pass" | tee -a ip_up.txt
}& # 并行放到后台运行
done
wait # 等待进程结束
# 将yunwei用户目录下的公钥推送到可以ping的服务器上
# 1.判断yunwei用户下有没有公钥
[ ! -f ~/.ssh/id_rsa.pub ] && ssh-keygen -P "" -f ~/.ssh/id_rsa &>/dev/null 2>&1
# 2.将id_rsa.pub公钥远程推送到指定服务器
# 2.1 判断expect程序是否安装,没安装则安装它
{
rpm -q expect
[ $? -ne 0 ] && sudo yum -y install expect &>/dev/null 2>&1
while read remote_ip pass
do
/usr/bin/expect <<-END
spawn ssh-copy-id root@$remote_ip
expect {
"yes/no" { send "yes\r";exp_continue }
"password:" { send "$pass\r" }
}
expect eof
END
done < ip_up.txt
}>/dev/null
wait
echo "公钥已经推送完毕,正在测试..."
# 测试验证
remote_ip=$(tail -1 ip_up.txt | cut -d' ' -f1)
ssh root@$remote_ip hostname &>/dev/null
test $? -eq 0 && echo "公钥成功推送完毕"
[root@openEuler-14 data]# bash a.sh
10.0.0.10 123456
10.0.0.11 123456
10.0.0.12 123456
10.0.0.13 123456
公钥已经推送完毕,正在测试...
公钥成功推送完毕
实战案例2:shell批量推送公钥脚本--优化
#!/bin/bash
>ip_up.txt
# 判断公钥是否存在
[ ! -f ~/.ssh/id_rsa.pub ] && ssh-keygen -P "" -f ~/.ssh/id_rsa &>/dev/null 2>&1
# 循环判断主机是否ping通,如果ping通则推送公钥
tr ':' ' ' < ip.txt | while read ip pass
do
{
ping -c1 $ip &>/dev/null
if [ $? -eq 0 ];then
echo $ip >> ip_up.txt
/usr/bin/expect <<-END &>/dev/null
spawn ssh-copy-id root@$ip
expect {
"yes/no" { send "yes\r";exp_continue }
"password:" { send "$pass\r" }
}
expect eof
END
fi
}&>/dev/null
done
wait
echo "公钥已经推送完毕,正在测试..."
# 测试验证
remote_ip=$(tail -1 ip_up.txt)
ssh root@$remote_ip hostname &>/dev/null
test $? -eq 0 && echo "公钥成功推送完毕"
实战案例3:多机互通--优化
[root@openEuler-14 ~]# cat ip.txt
10.0.0.10:123456
10.0.0.11:123456
10.0.0.12:123456
10.0.0.13:123456
#!/bin/bash
# 设置本机ip和密码
read -s -p "请输入本机的密码:" secret
echo # 换行,让后续输出从新行开始
# 检查密码是否为空
if [ -z "$secret" ]; then
echo "错误:密码不能为空!" >&2
exit 1
fi
# 可选:使用密码进行后续操作
echo "密码已设置,进行后续操作..."
echo "清空 ip_up.txt 文件"
> ip_up.txt
# 生成 SSH 密钥对,不输出信息到屏幕
echo "生成 SSH 密钥对"
ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa > /dev/null 2>&1
echo "检查 ip.txt 文件是否存在"
if [ ! -f "ip.txt" ]; then
echo "ip.txt 文件不存在,请检查。"
exit 1
fi
echo "判断是否安装expect"
! rpm -q expect &>/dev/null && { echo "未安装 expect,正在尝试安装..."; yum -y install expect &>/dev/null || { echo "错误:expect 安装失败,请检查网络连接或权限" >&2; exit 1; }; }
echo "将ssh密钥对复制到本地主机"
# 使用 expect 实现 ssh-copy-id 到本地主机
/usr/bin/expect <<-END &>/dev/null
spawn ssh-copy-id 127.1
expect {
"yes/no" { send "yes\r"; exp_continue }
"password:" { send "$secret\r" }
timeout {
puts "连接 127.0.0.1 超时,推送失败。"
exit 1
}
eof {
if { [regexp -nocase "Permission denied" \$expect_out(buffer)] } {
puts "认证失败: $ip"
exit 1
} else {
puts "密钥推送成功: $ip"
exit 0
}
}
}
expect eof
END
echo "start"
# 循环判断主机是否 ping 通,如果 ping 通则推送秘钥对
while IFS=: read -r ip pass; do
# 检查读取的行是否符合格式
if [ -z "$ip" ] || [ -z "$pass" ]; then
echo "ip.txt 文件中存在格式错误的行:$ip:$pass"
continue
fi
ping -c1 $ip &>/dev/null
if [ $? -eq 0 ];then
echo $ip $pass
echo $ip $pass >> ip_up.txt
/usr/bin/expect <<-END &>/dev/null
spawn rsync -avz /root/.ssh/ $ip:/root/.ssh/
expect {
"yes/no" { send "yes\r";exp_continue }
"password:" { send "$pass\r" }
timeout {
puts "连接 $ip 超时,推送失败。"
exit 1
}
eof {
if { [regexp -nocase "Permission denied" \$expect_out(buffer)] } {
puts "认证失败: $ip"
exit 1
} else {
puts "密钥推送成功: $ip"
exit 0
}
}
}
expect eof
END
fi
done < ip.txt
wait
echo "秘钥已经推送完毕,正在测试..."
echo "测试所有可达主机的公钥推送是否成功"
all_success=true
while read -r line; do
remote_ip=$(echo "$line" | cut -d' ' -f1)
ssh root@"$remote_ip" hostname &>/dev/null
if [ $? -ne 0 ]; then
echo "向 $remote_ip 推送公钥失败。"
all_success=false
fi
done < ip_up.txt
if $all_success; then
echo "秘钥成功推送完毕"
else
echo "部分主机公钥推送失败,请检查。"
fi
echo "endl"
[root@openEuler-14 ~]# bash a.sh
清空 ip_up.txt 文件
生成 SSH 密钥对
检查 ip.txt 文件是否存在
判断是否安装expect
expect-5.45.4-8.oe2403.x86_64
将ssh密钥对复制到本地主机
start
10.0.0.12 123456
10.0.0.13 123456
秘钥已经推送完毕,正在测试...
测试所有可达主机的公钥推送是否成功
秘钥成功推送完毕
endl
[root@Rocky9-12 ~]# bash ssh_expect.sh
请输入本机的密码:
密码已设置,进行后续操作...
清空 ip_up.txt 文件
生成 SSH 密钥对
检查 ip.txt 文件是否存在
判断是否安装expect
package expect is not installed
将ssh密钥对复制到本地主机
start
10.0.0.12 123456
10.0.0.13 123456
秘钥已经推送完毕,正在测试...
测试所有可达主机的公钥推送是否成功
秘钥成功推送完毕
endl
实战案例4:快速删除远程密钥对
在3的基础上,万一密钥对泄露,可以实现快速删除,更换秘钥对
[root@openEuler-14 ~]# cat ip_up.txt
10.0.0.12:123456
10.0.0.13:123456
#!/bin/bash
# 错误处理:如果命令执行失败,脚本将终止
set -e
echo "生成并清空 ip_del.txt 文件"
> ip_del.txt
> ip_success.txt
echo "检查 ip_up.txt 文件是否存在"
if [ ! -f "ip_up.txt" ]; then
echo "ip_up.txt 文件不存在,请检查。"
exit 1
fi
# 读取文件内容到数组
hosts=()
while IFS= read -r line; do
hosts+=("$line")
done < ip_up.txt
# 检查文件内容格式
for line in "${hosts[@]}"; do
remote_ip=$(echo "$line" | cut -d' ' -f1)
pass=$(echo "$line" | cut -d' ' -f2)
if [ -z "$remote_ip" ] || [ -z "$pass" ]; then
echo "ip_up.txt 文件格式错误:$line"
exit 1
fi
done
# 使用数组执行实际操作
all_success=true
sleep 1
for line in "${hosts[@]}"; do
remote_ip=$(echo "$line" | cut -d' ' -f1)
pass=$(echo "$line" | cut -d' ' -f2)
# 使用 expect 实现 秘钥快速删除
/usr/bin/expect <<-END &>/dev/null
set timeout 10
spawn ssh -o StrictHostKeyChecking=no root@$remote_ip "rm -rf ~/.ssh/*"
expect {
"yes/no" { send "yes\r";exp_continue }
"password:" { send "$pass\r" }
timeout {
puts "连接 $remote_ip 超时,删除失败。"
exit 1
}
eof {
if { [exp_status] != 0 } {
puts "删除 $remote_ip 密钥对失败。"
exit 1
}
}
}
expect eof
END
# 检查 expect 命令的退出状态
if [ $? -ne 0 ]; then
all_success=false
echo "$remote_ip 删除失败" >> ip_del.txt
else
echo "$remote_ip 删除成功" >> ip_success.txt
echo "$remote_ip 删除成功"
fi
done
wait
if [ "$all_success" = true ]; ; then
# 删除本机密钥对
rm -rf ~/.ssh
echo "秘钥对成功删除"
else
echo "部分秘钥对成功删除失败,请检查。"
fi
echo "删除结果统计:"
echo "成功: $(wc -l < ip_success.txt) 台主机"
echo "失败: $(wc -l < ip_del.txt) 台主机"
echo "endl"
注意:必须执行两次才可以
[root@Rocky9-12 ~]# bash ssh_del.sh
生成并清空 ip_del.txt 文件
检查 ip_up.txt 文件是否存在
[root@Rocky9-12 ~]# bash ssh_del.sh
生成并清空 ip_del.txt 文件
检查 ip_up.txt 文件是否存在
10.0.0.12 删除成功
10.0.0.13 删除成功
10.0.0.14 删除成功
10.0.0.15 删除成功
10.0.0.16 删除成功
10.0.0.17 删除成功
10.0.0.18 删除成功
10.0.0.19 删除成功
10.0.0.20 删除成功
秘钥对成功删除
删除结果统计:
成功: 9 台主机
失败: 0 台主机
endl
评论