使用2>&1与tee赋值变量时Bash脚本偶发挂起问题排查
SSH隧道脚本挂起问题的原因与解决方法
问题场景
在Windows的WSL 2 Ubuntu 20.04环境下,编写Bash脚本批量创建SSH隧道访问数据库,需求是静默运行,仅出错时输出信息。当前遇到两个矛盾情况:
- 使用
2>&1配合tee时,出错场景正常,但SSH连接成功时脚本挂起,echo "SSH Finished"始终不执行; - 移除
2>&1仅保留tee,成功和失败场景均正常,但错误信息无法被变量$ssh捕获。
经排查,挂起仅发生在登录时带有主机消息的SSH服务器上,直接执行SSH命令可正常连接返回命令行,但脚本中会挂起,尝试过&和-f参数无效。
相关代码片段:
#!/bin/bash # add -x to above to enable debug ... exec 5>&1 echo "Start SSH" ssh=$(echo 0 | ssh -N -f -o StrictHostKeyChecking=accept-new -L $LocalDBPort:"$RemoteDBHost":5432 user@"$SSHServer" 2>&1 | tee /dev/fd/5) echo "SSH Finished" if [[ $ssh == *"AccessDeniedException"* ]] || [[ $ssh == *"Could not request local forwarding"* ]] || [[ $ssh == *"failed"* ]] || [[ $ssh == *"Bad"* ]] || [[ $ssh == *"error"* ]] || [[ $ssh == *"exception"* ]] || [[ $ssh == *"Permission denied"* ]] || [[ $ssh == *"already in use"* ]] then echo "$ssh" Connected=No fi
问题原因
-f参数与标准输入的冲突:-f会让SSH认证完成后后台运行,但echo 0 | ssh ...给SSH提供的管道输入,会被后台化的SSH进程持有,导致tee一直等待管道关闭,命令替换$()无法结束,脚本挂起。- 主机消息的干扰:带登录消息的服务器,SSH认证完成后会向输出流发送消息,结合
-f和管道的特性,这些输出未被完全读取,导致tee收不到EOF,持续等待进而阻塞脚本。
解决方案
方案1:关闭SSH标准输入,避免管道阻塞
去掉echo 0 |,改用< /dev/null让SSH标准输入指向空设备,后台化后不会持有管道读端:
ssh=$(ssh -N -f -o StrictHostKeyChecking=accept-new -L $LocalDBPort:"$RemoteDBHost":5432 user@"$SSHServer" < /dev/null 2>&1 | tee /dev/fd/5)
方案2:禁用SSH登录消息+关闭标准输入
通过-o LogLevel=ERROR让SSH仅输出错误信息,同时配合< /dev/null,既避免登录消息干扰,又满足静默运行需求:
ssh=$(ssh -N -f -o StrictHostKeyChecking=accept-new -o LogLevel=ERROR -L $LocalDBPort:"$RemoteDBHost":5432 user@"$SSHServer" < /dev/null 2>&1 | tee /dev/fd/5)
方案3:用进程替代分离输出流
如果上述方法仍有问题,用进程替代确保tee能正确收到EOF:
exec 5>&1 ssh=$(ssh -N -f -o StrictHostKeyChecking=accept-new -L $LocalDBPort:"$RemoteDBHost":5432 user@"$SSHServer" < /dev/null 2>&1 > >(tee /dev/fd/5))
验证说明
修改后,SSH成功后台运行时,输出流会被正确读取并关闭,tee正常结束,脚本继续执行后续代码;出错时,错误信息会同时被捕获到$ssh变量并输出到终端,完全符合需求。
内容的提问来源于stack exchange,提问作者rcmpayne
相关产品推荐
相关产品推荐

