You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何整合Bash与Expect脚本实现批量SSH公钥自动分发?

如何整合Bash与Expect脚本实现批量SSH公钥自动分发?

我明白你的困扰啦——想用Bash批量遍历主机列表,同时借助Expect自动输入密码完成ssh-copy-id的交互,对吧?你的单独Expect脚本能搞定单主机,但整合进Bash循环就失效,核心原因是Bash本身无法解析Expect的专属语法,直接用here-doc把Expect代码丢给Bash执行肯定行不通。下面给你两个实用的整合方案,都是我实际用过的:

方案一:在Bash脚本中内嵌Expect逻辑(单脚本搞定)

这种方式不需要单独写Expect脚本,直接在Bash循环里用expect -c命令执行Expect代码,把Bash的变量传递进去就行,非常紧凑:

#!/bin/bash

invdir=/home/john/inventory

# 先检查参数是否齐全,比原脚本更健壮
if [ -z "$1" ] || [ -z "$2" ]; then
    echo "你必须输入两个参数:环境名称(prod, dev, dr, test)和对应环境的密码"
    exit 1
fi

# 检查指定环境的主机列表文件是否存在
host_file="${invdir}/${1}"
if [ ! -f "$host_file" ]; then
    echo "错误:环境 $1 的主机列表文件不存在!"
    exit 1
fi

# 循环读取主机列表,处理每个主机
while IFS= read -r host; do
    # 跳过空行和注释行(如果你的列表里有注释的话)
    [[ "$host" =~ ^#.*$ || -z "$host" ]] && continue
    
    echo "正在处理主机:$host"
    # 用expect -c执行交互逻辑,双引号让Bash变量能传递到Expect里
    expect -c "
        spawn ssh-copy-id -i ~/.ssh/id_rsa.pub $host
        expect {
            # 处理密码输入提示
            \"password:\" { send \"$2\n\"; exp_continue }
            # 处理第一次连接时的主机指纹确认提示
            \"Are you sure you want to continue connecting (yes/no)?\" { send \"yes\n\"; exp_continue }
            # 等待命令执行完成
            eof
        }
    "
done < "$host_file"

关键细节说明:

  • 用while IFS= read -r host读取文件,避免了原脚本中for host in \cat $file``遇到主机名带空格就拆分的问题
  • 增加了主机指纹确认的处理,第一次连接新主机时不会卡住
  • 提前做了参数和文件存在性检查,出错时能给出明确提示

方案二:Bash+独立Expect脚本(逻辑分离更清晰)

如果觉得内嵌Expect代码太乱,可以把交互逻辑单独写成Expect脚本,然后在Bash循环里调用它:

第一步:写独立的Expect脚本(比如命名为ssh-copy-id-auto.exp)

#!/usr/bin/expect -f

# 检查参数数量是否正确
if { $argc != 2 } {
    puts "用法:$argv0 <主机地址> <密码>"
    exit 1
}

# 接收Bash传递过来的参数
set host [lindex $argv 0]
set password [lindex $argv 1]

# 执行ssh-copy-id并处理交互
spawn ssh-copy-id -i ~/.ssh/id_rsa.pub $host
expect {
    "password:" { send "$password\n"; exp_continue }
    "Are you sure you want to continue connecting (yes/no)?" { send "yes\n"; exp_continue }
    eof
}

给脚本加上执行权限:

chmod +x ssh-copy-id-auto.exp

第二步:修改你的Bash脚本,循环调用这个Expect脚本

#!/bin/bash

invdir=/home/john/inventory

if [ -z "$1" ] || [ -z "$2" ]; then
    echo "你必须输入两个参数:环境名称(prod, dev, dr, test)和对应环境的密码"
    exit 1
fi

host_file="${invdir}/${1}"
if [ ! -f "$host_file" ]; then
    echo "错误:环境 $1 的主机列表文件不存在!"
    exit 1
fi

# 循环遍历主机,调用Expect脚本
while IFS= read -r host; do
    [[ "$host" =~ ^#.*$ || -z "$host" ]] && continue
    echo "正在处理主机:$host"
    ./ssh-copy-id-auto.exp "$host" "$2"
done < "$host_file"

这个方案的优点:

  • 逻辑分离,Expect脚本专注处理交互细节,Bash脚本专注批量遍历,可读性更强
  • 以后如果要修改交互逻辑,直接改Expect脚本就行,不用动Bash部分

一些注意事项

  • 确保你的系统已经安装了Expect:Debian/Ubuntu用sudo apt install expect,CentOS/RHEL用sudo yum install expect
  • 密码作为参数传递时,要注意安全性——虽然脚本里不会明文存储,但执行时ps命令可能会看到密码,敏感环境可以考虑用环境变量传递
  • 主机列表文件里每行一个主机,格式可以是user@host(指定远程用户)或者直接host(默认用当前用户名)

备注:内容来源于stack exchange,提问作者JKC

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.22 11:44:29