在Shell脚本中复用Expect生成的SSH会话的可行性咨询
如何在Shell脚本中复用Expect创建的SSH会话?
我想在Shell脚本里执行多段Expect代码,且各段Expect之间能运行Shell命令。以下是一个概念性的非工作示例:第一段Expect用于SSH连接远程节点,后续代码段希望在同一个远程会话中执行其他命令:
#!/bin/bash # 第一段Expect代码 /usr/bin/expect - <<EOF global spawn_id spawn /bin/bash expect -re ".*\$" spawn -- "ssh -oStrictHostKeyChecking=no -oCheckHostIP=no admin@localstack1\r" expect -re ".*assword.*" send 'admin\r' expect -re ".*\$" interact EOF # 运行Shell命令 ls -lrt # 第二段Expect代码 /usr/bin/expect - <<EOF expect -re ".*\$" send -- "hostname\r" expect -re ".*\$" EOF
当前方案无法实现,想请教:为什么不像pexpect那样,找不到复用已生成Shell会话(传递子对象)的方法?另外,命令列表、预期结果和超时时间均由外部程序提供,无法硬编码到Expect脚本中,有时仅需一组命令,有时则需要多组。
为什么无法跨Expect进程复用会话?
每一次/usr/bin/expect调用都是独立的操作系统进程,进程之间的资源(包括spawn_id对应的会话句柄)是完全隔离的。第一段Expect执行结束后,它创建的SSH会话要么因interact转交给用户,要么随Expect进程退出而被关闭,第二段Expect进程根本无法访问前一段的会话资源。
可行解决方案
1. 单Expect进程+外部参数传递(推荐)
将所有Expect逻辑整合到单个Expect进程中,通过环境变量或命令行参数把外部提供的命令、预期结果、超时时间传入,让Expect在同一个SSH会话内依次执行所有操作。示例如下:
#!/bin/bash # 模拟从外部程序获取命令、预期结果、超时时间(实际中替换为外部数据来源) COMMANDS=("hostname" "df -h") EXPECT_PATTERNS=(".*\$" ".*\$") TIMEOUTS=(10 15) # 将数组转为Expect可识别的字符串(用|分隔) CMD_LIST=$(IFS='|'; echo "${COMMANDS[*]}") EXP_LIST=$(IFS='|'; echo "${EXPECT_PATTERNS[*]}") TOUT_LIST=$(IFS='|'; echo "${TIMEOUTS[*]}") # 调用单个Expect进程执行所有操作 /usr/bin/expect -c " # 拆分传入的参数为数组 set commands [split \"$CMD_LIST\" \"|\"] set expect_patterns [split \"$EXP_LIST\" \"|\"] set timeouts [split \"$TOUT_LIST\" \"|\"] # 初始化SSH连接 spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no admin@localstack1 expect -re \".*assword.*\" send \"admin\r\" expect -re \".*\$\" # 循环执行所有命令 for {set i 0} {\$i < [llength \$commands]} {incr i} { set cmd [lindex \$commands \$i] set pattern [lindex \$expect_patterns \$i] set timeout_val [lindex \$timeouts \$i] set timeout \$timeout_val send \"\$cmd\r\" expect -re \$pattern } # 可选:退出远程会话或进入交互模式 # send \"exit\r\" # expect eof # interact " # 中间执行Shell命令 ls -lrt
2. 简化场景:改用sshpass批量执行
如果仅需执行简单远程命令,无复杂交互需求,可以用sshpass直接批量执行,虽然每次会重建SSH连接,但实现简单:
#!/bin/bash # 第一组远程命令 sshpass -p "admin" ssh -oStrictHostKeyChecking=no -oCheckHostIP=no admin@localstack1 <<REMOTE_CMDS hostname df -h REMOTE_CMDS # 中间执行Shell命令 ls -lrt # 第二组远程命令 sshpass -p "admin" ssh -oStrictHostKeyChecking=no -oCheckHostIP=no admin@localstack1 <<REMOTE_CMDS uptime free -m REMOTE_CMDS
3. 复杂交互场景:用文件/管道传递会话控制(不推荐)
如果必须在Expect执行中途切换到Shell,可以通过命名管道或临时文件传递会话状态,但实现复杂且可靠性低,仅适合特殊场景,一般不建议使用。
内容的提问来源于stack exchange,提问作者monk
相关产品推荐
相关产品推荐

