如何在Perl脚本中集成expect实现SSH自动输密执行远程命令
集成失败核心原因
直接替换system调用后运行失败,基本是三个问题导致的:
- 测试用的expect命令末尾带了
interact指令:这个指令的作用是将程序控制权交还给交互终端,供手动操作使用,而Perl脚本(尤其是当前这个CGI类Web脚本)执行system调用时没有绑定可交互的终端环境,走到这步会直接卡死进程。 - 参数转义缺失:传入的
$address是邮箱格式,自带@特殊字符,直接拼接到shell命令中会被Perl或shell解析成变量标识,导致实际传给远程脚本的参数错位、缺失,甚至命令直接报错。 - 后台符
&位置错误:原写法把&放在ssh命令末尾,会导致spawn启动的ssh进程直接进入后台,expect根本捕获不到密码输入提示,流程直接断在等待密码的步骤。
正确集成方案
替换原脚本中system ("ssh SERVERNAME /test/abc.csh $lot $$ $address &");这一段即可,完整代码如下:
if (param("lremail") && param("lot")) { my $address=param("lremailaddress"); my $lot=param("lot"); print a({-href=>"$dir/new.pl"},"Back to Top"),br; print "Request submitted for $address.",br; print "Lot $lot",br; print "You will receive an e-mail with a link to the data when the request is complete."; print end_html; # 对传入参数做转义,避免特殊字符解析错误 my $safe_lot = quotemeta($lot); my $safe_pid = quotemeta($$); my $safe_addr = quotemeta($address); # 构造expect命令,非交互场景不要用interact my $expect_cmd = qq{expect -c ' set timeout 300 spawn ssh -o StrictHostKeyChecking=no SERVERNAME /test/abc.csh $safe_lot $safe_pid $safe_addr expect { "Password:" { send "替换成你的实际登录密码\r" exp_continue } eof } ' > /dev/null 2>&1 &}; system($expect_cmd); exit(1); }
关键修改说明
- 把原expect命令的
interact替换为expect eof:非交互场景下不需要接管终端,等待ssh连接断开、远程命令执行结束后自动退出expect进程即可。 - 增加
set timeout 300配置:设置5分钟超时阈值,避免网络波动、远程脚本挂死时expect进程永久驻留占用资源。 - 用
quotemeta()对所有动态参数做转义:自动转义参数里的特殊字符,避免邮箱@、参数带空格等场景下命令解析错误。 - 调整后台符
&位置:把整个expect进程放到后台执行,不阻塞当前Perl脚本的退出流程,同时加了/dev/null重定向丢弃expect的输出,避免多余日志产生。 - 给ssh加了
-o StrictHostKeyChecking=no参数:第一次连接远程主机时不会弹主机指纹确认提示,避免额外的阻塞点。
更优替代方案
不建议长期用明文密码写在脚本里的方案,优先配置SSH密钥免密登录:把运行Perl脚本的用户的公钥追加到远程SERVERNAME对应用户的
~/.ssh/authorized_keys文件中,配置完成后根本不需要expect,直接把system调用换成下面的命令即可,稳定性和安全性都高很多:system("ssh -o BatchMode=yes -o StrictHostKeyChecking=no SERVERNAME /test/abc.csh $lot $$ $address >/dev/null 2>&1 &");
内容的提问来源于stack exchange,提问作者prad001
相关产品推荐
相关产品推荐

