Bash并行ping监控IP列表主机上下线脚本开发及问题排查
问题根因
你写的脚本持续监控模块异常的核心原因有3个:
- 多进程逻辑使用场景错误:你给每个IP单独启动了一个常驻的
check_health子进程,每个子进程的内存空间完全独立,既无法维护全量IP的状态映射,xargs按行传参时遇到空行、特殊字符还会出现参数丢失,最终表现为仅部分IP被监控。 - ping参数跨平台不兼容:脚本里用的
-t 1是BSD/macOS版本ping的TTL参数,Linux系统下ping的超时参数为-W 1,参数错误会导致ping检测结果完全不准。 - 状态存储逻辑缺陷:原参考脚本2用
up+序号的动态变量存状态,一旦IP列表读取顺序错乱、漏行,就会出现IP和状态不匹配的问题。
优化方案
直接重构逻辑,同时满足两个参考脚本的核心能力:
- 读取IP文件时一次性过滤空行、
#开头的注释行,存入全局数组,从根源避免读文件漏行问题。 - 首次存活检测保留xargs多进程并行逻辑,默认8个并发(可自定义)快速完成初始状态扫描。
- 持续监控阶段每轮检测仍用多进程并行ping,所有检测结果返回主进程,主进程用关联数组统一维护每个IP的上一次状态,仅当状态发生变化时输出日志,既保证检测速度,又不会出现状态隔离、IP漏检的问题。
- 做ping参数跨平台兼容,自动识别系统类型适配参数,Linux、macOS都能正常跑。
- 保留原脚本的彩色输出、小黄鸭启动界面,去掉冗余的重复函数。
修复后可直接运行的脚本
#!/bin/bash set -eo pipefail trap 'echo -e "\n\033[1;33m监控已退出\033[0m"; exit 0' INT TERM # 入参检查 if [ $# -lt 1 ]; then echo "用法: $0 <IP列表文件路径> [并行进程数,默认8]" exit 1 fi HOST_FILE=$1 MAX_PROC=${2:-8} # 加载IP列表,过滤空行和注释行 mapfile -t HOST_LIST < <(grep -vE '^\s*$|^\s*#' "$HOST_FILE") if [ ${#HOST_LIST[@]} -eq 0 ]; then echo "错误: IP列表文件为空或不存在" exit 1 fi # 跨平台ping函数 ping_host() { local ip=$1 if [[ "$(uname)" == "Linux" ]]; then ping -c 1 -W 1 "$ip" >/dev/null 2>&1 else # macOS/BSD ping -c 1 -W 1 -t 1 "$ip" >/dev/null 2>&1 fi echo "$ip $?" } export -f ping_host # 彩色输出函数 print_log() { local ip=$1 local status=$2 local date_str date_str=$(date '+%d/%m/%Y %H:%M:%S') if [ $status -eq 0 ]; then echo -e "|\033[1;36m $date_str \033[0m" "|\033[1;32m Node |"$ip "| UP \033[0m" | column -t -s "|" else echo -e "|\033[1;36m $date_str \033[0m" "|\033[1;31m Node |"$ip "| DOWN \033[0m" | column -t -s "|" fi } # 启动欢迎界面 print_banner() { local textpink textyellow external_ip internal_ip my_name textpink=$(tput setaf 5) textyellow=$(tput setaf 11) external_ip=$(curl -s --connect-timeout 2 ifconfig.me || echo "获取失败") internal_ip=$(ip addr 2>/dev/null | grep -Eo 'inet ([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' | head -1 || ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' | head -1) my_name=$(hostname) clear echo -e "${textpink}================================================================" cat << "EOF" __ __ ___( o)> DuckLab <(o )___ \ <_. ) Monitor ( ._> / `---' `---' EOF echo -e "${textpink}================================================================" echo -e "${textyellow} Press <CTRL+C> to exit. " echo -e "${textpink}================================================================" echo -e "\033[1;36m $internal_ip \033[0m" " ${textpink}| " "\033[1;36m $my_name \033[0m" " ${textpink}| " "\033[1;36m $external_ip \033[0m" echo -e "${textpink}================================================================" echo -e "已加载 ${#HOST_LIST[@]} 个监控目标, 并行进程数: $MAX_PROC" echo -e "-------------------------------------------------------------" } # 首次并行检测 first_scan() { echo "正在执行首次存活检测..." while read -r ip ret; do print_log "$ip" "$ret" LAST_STATUS[$ip]=$ret done < <(printf "%s\n" "${HOST_LIST[@]}" | xargs -n 1 -P "$MAX_PROC" -I{} bash -c 'ping_host "$@"' _ {}) echo -e "-------------------------------------------------------------" echo "首次检测完成, 进入持续监控模式, 仅状态变更时输出日志:" } # 主逻辑 declare -A LAST_STATUS print_banner first_scan # 持续监控循环 while true; do # 并行获取所有IP当前状态 while read -r ip ret; do # 状态变化才输出 if [ "${LAST_STATUS[$ip]}" != "$ret" ]; then print_log "$ip" "$ret" LAST_STATUS[$ip]=$ret fi done < <(printf "%s\n" "${HOST_LIST[@]}" | xargs -n 1 -P "$MAX_PROC" -I{} bash -c 'ping_host "$@"' _ {}) sleep 1 done
使用说明
- IP列表文件里每行写一个IP,支持空行、
#开头的注释行,脚本会自动过滤。 - 运行命令:
chmod +x ping_monitor.sh && ./ping_monitor.sh ip_list.txt,如果要自定义并行数,在文件路径后面加数字就行,比如./ping_monitor.sh ip_list.txt 16就是16个并发。 - 按Ctrl+C即可正常退出脚本。
内容的提问来源于stack exchange,提问作者Tom
相关产品推荐
相关产品推荐

