expect 实现 Linux 免交互自动化:命令速查与两个 SSH/SCP 脚本

批量给一堆机器分发公钥、拷文件,脚本走到要输密码或 yes/no 的地方就会整个卡住。expect 就是解决这个问题的:它模拟用户输入、读取程序的输出,匹配到提示就自动应答。最早是为 Telnet、SSH、FTP 这类网络服务的自动化交互设计的,任何需要模拟人工敲键盘的场合都能用。

能力与边界

  1. 自动化交互:写脚本模拟用户行为,输入文本、等待特定输出、匹配文本、从输出里提取信息。
  2. 处理交互式程序:对需要人工确认的命令行程序(Telnet、SSH、FTP 等)自动登录、执行命令、处理输出。
  3. 条件匹配:根据输出里的文本模式决定下一步动作,一个脚本能处理多种情况。
  4. 脚本语言:本身支持流程控制、变量、函数,可读性和可维护性都在。
  5. 模块化:除了命令行,还有 expect 模块可以嵌进 Tcl、Perl、Python,集成到更大的自动化任务里。
  6. 跨平台:各种 Unix/Linux 系统都有,也有若干变体和替代工具。

写脚本的思路一致:把预期的交互步骤排进去,等待输出、按输出决定下一步。不过有些场景下更现代的工具更合适,比如 Python 的 pexpect 库。

安装

expect 依赖 tcl,先确认装没装:

[root@localhost ~]# whereis tcl
tcl: /usr/lib64/tcl8.5 /usr/share/tcl8.5

没有就先补 tcl 再装 expect:

[root@localhost ~]# yum install -y tcl
[root@localhost ~]# yum install -y expect

确认安装路径:

[root@localhost ~]# command -v expect
/usr/bin/expect

常用命令速查

  1. spawn <command>:启动新的交互式进程,后面跟要执行的命令。
  2. expect <pattern> {action}:等输出里匹配到 pattern 就执行 action,可以多次使用匹配不同模式。
  3. send <string>:向当前进程发送字符串。
  4. send -- "<string>":发送含特殊字符的字符串,防止被解释。
  5. exp_send <string>:功能同 send。
  6. exp_continue:在 expect 块里继续匹配后续模式,而不是结束执行。
  7. send_user <string>:打印信息,相当于脚本里的 echo。
  8. interact:把控制权交还给用户,允许手动交互。
  9. exit:退出 expect 脚本。
  10. eof:表示脚本执行结束。
  11. set <variable> <value>:定义变量并赋值。
  12. puts <string>:输出字符串。
  13. set timeout <seconds>:设超时时间,超时未匹配就走超时处理。

实战一:免交互拷贝公钥

给新机器批量铺 SSH 公钥,脚本长这样:

[root@localhost ~]# vim autocopy.exp
#!/usr/bin/expect

# 设置超时时间为10秒
set timeout 10

# 从命令行参数获取远程主机名和密码
set user_hostname [lindex $argv 0]
set password [lindex $argv 1]

# 启动 ssh-copy-id 命令,并将远程主机名传递给它
spawn ssh-copy-id $user_hostname

# 使用 expect 进行模式匹配和交互操作
expect {
    "(yes/no)?" {
        # 如果遇到 "(yes/no)?" 提示,自动发送 "yes" 并等待密码提示
        send "yesn"
        expect "*assword:" {
            # 发送密码并等待完成
            send "$passwordn"
        }
    }
    "*assword:" {
        # 如果直接遇到密码提示,发送密码并等待完成
        send "$passwordn"
    }
}

# 等待并处理脚本执行结束
expect eof

[root@localhost ~]# chmod +x autocopy.exp

流程拆解:超时设 10 秒;从命令行参数拿远程主机名和密码;spawn ssh-copy-id;expect 块分别处理首次连接的 “(yes/no)?” 提示(先回 yes 再等密码提示)和直接的 “*assword:” 提示;最后 expect eof 等执行结束。

调用方式:

[root@localhost ~]# ./autocopy.exp root@192.168.66.100 密码

执行后公钥直接写入对端,全程没有人工输入:

image-73

实战二:scp 自动应答

同样的骨架换成交互 scp,多传源文件、目标文件两个参数,并等 “100%” 出现确认拷贝完成:

[root@localhost ~]# vim autocopy.exp
#!/usr/bin/expect

# 设置超时时间为10秒
set timeout 10

# 从命令行参数获取远程主机名、源文件、目标文件和密码
set user_hostname [lindex $argv 0]
set src_file [lindex $argv 1]
set dest_file [lindex $argv 2]
set password [lindex $argv 3]

# 启动 scp 命令,将源文件复制到远程主机的目标位置
spawn scp $src_file $user_hostname:$dest_file

# 使用 expect 进行模式匹配和交互操作
expect {
    "(yes/no)?" {
        # 如果遇到 "(yes/no)?" 提示,自动发送 "yes" 并等待密码提示
        send "yesn"
        expect "*assword:" {
            # 发送密码并等待完成
            send "$passwordn"
        }
    }
    "*assword:" {
        # 如果直接遇到密码提示,发送密码并等待完成
        send "$passwordn"
    }
}

# 等待复制完成的 "100%" 提示
expect "100%"

# 等待并处理脚本执行结束
expect eof

[root@localhost ~]# chmod +x autocopy.exp

用法:autoscp.exp [user@]hostname 源文件 目标目录 [password]。这个脚本对已经做过公钥认证的机器也能跑,因为公钥认证默认优先于密码认证,password 项是可选的;反过来在没做公钥认证的机器上 password 是必选,不然免交互就没有意义了。

注意事项

密码走命令行参数明文传递,在机器上用 ps 或 shell history 都能看到,批量脚本里更稳妥的做法是把密码放进受限权限的文件再读取,或者干脆先铺公钥、后续全用密钥。两个脚本记得 chmod +x。timeout 按目标机器的响应速度调,网络差的环境 10 秒可能不够,匹配不到提示脚本会提前退出。

© 版权声明
THE END
喜欢就支持一下吧
点赞12 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容