Shell 脚本 while 循环只执行一次的问题
2025-03-26 tech linux shell ssh 5 mins 1921 字
最近在做 Prometheus 告警的自动化运维场景,我通过 Shell 脚本循环处理多个 long_uptime
告警。然而,脚本在首次执行 SSH 命令后直接退出循环,导致后续告警未被处理。本文记录完整的排错过程。
问题现象
原始代码片段:
while read -r alert; do
# 提取告警信息...
ssh "$node" "docker restart $container_name"
# 其他逻辑...
done <<< "$alerts"
表现:
- 循环仅处理第一个告警后退出,未遍历所有符合条件的告警。
- 取消注释
ssh
命令后问题消失,证明与 SSH 执行相关。
原因分析
1. SSH 阻塞导致循环中断
- 默认行为:SSH 会读取标准输入(stdin),可能导致后续的
read
命令获取到空值 - 错误传播:若 SSH 连接失败且未处理退出码,Bash 可能因
set -e
或默认行为终止脚本
2. 缺乏并发与超时控制
- 串行执行:每个 SSH 命令需等待前一个完成,若节点响应慢,总耗时过长
- 超时缺失:网络波动或节点宕机时,SSH 可能无限期挂起
解决方案
1. 非阻塞 SSH 执行
ssh -n -o ConnectTimeout=10 "$node" "docker restart $container_name" </dev/null &>/dev/null &
- 关键参数:
-n
:禁用 stdin 输入ConnectTimeout=10
:10秒连接超时&
:后台执行,立即返回控制权
2. 并发控制与错误处理
max_jobs=5 # 最大并发数
while read -r alert; do
# 处理告警逻辑...
ssh "$node" "docker restart $container_name" &
# 控制并发
if [[ $(jobs -r -p | wc -l) -ge $max_jobs ]]; then
wait -n # 等待任意一个任务完成
fi
done <<< "$alerts"
wait # 等待所有后台任务
jobs -r -p
:获取当前运行的后台任务 PIDwait -n
:避免资源耗尽,动态控制并发数
3. 错误容忍设计
if ! ssh "$node" "docker restart $container_name"; then
echo "Failed to restart $container_name on $node" >&2
continue # 跳过失败任务,继续处理后续告警
fi
continue
:即使单个 SSH 失败,仍继续循环
验证步骤
-
模拟多告警输入:
alerts='[ {"labels": {"category": "long_uptime", "name": "app1", "node": "node1"}}, {"labels": {"category": "long_uptime", "name": "app2", "node": "node2"}} ]'
确认脚本处理所有告警
-
压力测试:
使用tc
模拟高延迟网络,观察脚本是否仍正常执行tc qdisc add dev eth0 root netem delay 2000ms
最终优化代码
while read -r alert; do
# 提取变量(省略部分代码)
if [[ "$category" == "long_uptime" ]]; then
# 并行执行 SSH 命令
ssh -n -o ConnectTimeout=10 "$node" "docker restart $container_name" </dev/null &>/dev/null &
# 控制并发(示例:最大 10 个并行任务)
if [[ $(jobs -r -p | wc -l) -ge 10 ]]; then
wait -n
fi
fi
done <<< "$alerts"
wait # 等待所有后台任务完成