标签搜索

expect基本使用

lilymaxyz
2025-04-28 / 0 评论 / 34 阅读 / 正在检测是否收录...

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

endl

0

评论

博主关闭了所有页面的评论