SSH批量连接测试实战
运维生涯中最打脸的经历本以为一行ssh命令就能搞定的事折腾了整整一下午。200多个IP脚本跑了4个就卡死手动测试明明能连上自动化脚本却疯狂报错好不容易跑通了速度又慢得让人崩溃……这篇文章记录了我从“这有什么难的”到“原来水这么深”的全过程。如果你也需要批量测试服务器连接性这篇实战避坑手册能帮你省下至少一个下午的调试时间。一、需求一个看似简单的任务事情是这样的某天下午领导扔过来一个IP列表大概200多行要求确认这些服务器的SSH连接是否正常。账号密码统一只需要测试连通性2-3秒超时结果按成功/失败分类保存。听起来很简单对吗我当时也是这么想的。原始需求拆解测试一批IP的22端口SSH连接固定账号root固定密码mn1502099A超时控制2-3秒结果分类输出成功IP / 失败IP支持批量数百个IP环境约束操作系统CentOS 7网络存在不确定延迟和防火墙策略性质测试环境可使用密码认证二、技术选型动手之前先评估一下有哪些路可以走。方案实现方式优点缺点适用场景sshpass 循环sshpass -p 密码 ssh rootIP简单直接无额外依赖串行执行数百IP耗时数分钟少量IP测试expect脚本spawn sshexpect password:交互处理稳定语法晦涩调试困难需要复杂交互的场景Python Paramikoparamiko.SSHClient().connect()功能全面错误处理完善需要Python环境依赖安装需要后续二次开发的场景GNU parallel并行parallel -j 20速度极快并发控制复杂文件写入冲突大量IP追求效率我最终选择了sshpass 循环作为基础方案原因很简单环境最干净不需要装任何额外的东西。等基础跑通了再考虑并行优化。三、踩坑实录坑1脚本只跑了4个IP就停了现象执行./ssh_test.sh只处理了4个IP就莫名其妙退出没有任何错误提示。排查加set -x调试发现报错./ssh_test.sh: line 1: i#!/bin/bash: No such file or directory原因脚本文件是从Windows记事本复制过来的保存成了带BOM头的UTF-8格式#!/bin/bash前面多了看不见的垃圾字符。解决方案# 查看文件编码 file ssh_test.sh # 方法一用 sed 清除第一行的垃圾字符 sed -i 1s/^.*#!/#!/ ssh_test.sh # 方法二用 dos2unix 转换同时修复换行符问题 yum install -y dos2unix dos2unix ssh_test.sh教训从Windows环境复制脚本到Linux永远先执行dos2unix。坑2IP文件有200行脚本只读了4行现象确认脚本本身没问题但while read循环只执行了4次就结束了。原因IP文件也是从Windows复制过来的每行末尾带着\rCRLF换行符。read命令读到\r后把它当作行内容的一部分导致后续逻辑异常。解决方案# 检查文件中的隐藏字符 cat -A iplist.txt | head -5 # 如果看到 ^M$说明是Windows换行符 # 修复换行符 dos2unix iplist.txt # 或者用 sed sed -i s/\r$// iplist.txt教训任何从外部来的文本文件先做格式清洗。坑3手动SSH能通脚本却一直失败现象手动执行ssh rootIP能正常连接但脚本里的sshpass命令始终返回非0。排查对比手动和脚本的SSH选项差异。最终发现是-o BatchModeyes这个选项在作祟——它会禁用密码认证导致sshpass根本无法工作。解决方案# 错误配置 sshpass -p 密码 ssh -o BatchModeyes rootIP # 正确配置明确允许密码认证 sshpass -p 密码 ssh \ -o BatchModeno \ -o PasswordAuthenticationyes \ -o StrictHostKeyCheckingno \ rootIP exit教训SSH选项不是越多越好BatchModeyes在自动化密码认证场景下是致命的。坑4并发写入导致结果文件错乱现象改成并行执行后结果文件里的IP数量对不上有的IP重复有的IP丢失。原因多个进程同时向同一个文件追加内容没有加锁导致写入冲突。解决方案每个进程写入独立的临时文件最后合并。TEMP_DIR$(mktemp -d) # 每个进程写入 $TEMP_DIR/success_$$ # 最后 cat $TEMP_DIR/success_* success.txt四、最终稳定版脚本经过四轮迭代最终版本具备以下特性环境预检sshpass是否安装、IP文件是否存在IP文件智能清洗去注释、去空行、去Windows换行符、格式校验进度显示百分比 动态更新结果分类输出带时间戳防止覆盖统计报告总数、成功数、失败数、成功率临时文件自动清理#!/bin/bash # SSH批量连接测试脚本 - 最终稳定版 set -u # 使用未定义变量时报错 set -e # 遇到错误时退出关键函数单独处理 # 配置参数 PASSWORDmn1502099A CONNECT_TIMEOUT3 IP_FILE${1:-iplist.txt} # 支持命令行参数 TIMESTAMP$(date %Y%m%d_%H%M%S) SUCCESS_FILEsuccess_ips_${TIMESTAMP}.txt FAILED_FILEfailed_ips_${TIMESTAMP}.txt # 环境验证 validate_environment() { if ! command -v sshpass /dev/null; then echo sshpass 未安装 echo 请执行: sudo yum install -y epel-release sshpass exit 1 fi if [ ! -f $IP_FILE ]; then echo IP文件 $IP_FILE 不存在 exit 1 fi } # IP列表清洗 prepare_ip_list() { local input_file$1 local output_file$2 $output_file while IFS read -r line || [ -n $line ]; do # 1. 剥离Windows换行符 line$(echo $line | tr -d \r) # 2. 去除首尾空白 line$(echo $line | sed s/^[[:space:]]*//;s/[[:space:]]*$//) # 跳过空行和注释 [[ -z $line || $line ~ ^# ]] continue # 提取IP移除可能的尾随注释 ip$(echo $line | awk {print $1}) # 校验IPv4格式 if [[ $ip ~ ^[0-9]\.[0-9]\.[0-9]\.[0-9]$ ]]; then echo $ip $output_file else echo 跳过无效IP: $ip 2 fi done $input_file } # 单IP测试 test_single_ip() { local ip$1 timeout ${CONNECT_TIMEOUT} sshpass -p $PASSWORD ssh \ -o ConnectTimeout${CONNECT_TIMEOUT} \ -o StrictHostKeyCheckingno \ -o PasswordAuthenticationyes \ -o BatchModeno \ -o LogLevelERROR \ root$ip exit 0 2/dev/null return $? } # 主流程 main() { echo echo SSH批量连接测试工具 echo echo 账号: root echo 超时: ${CONNECT_TIMEOUT}秒 echo IP文件: $IP_FILE echo 成功输出: $SUCCESS_FILE echo 失败输出: $FAILED_FILE echo validate_environment # 清洗IP列表 local clean_ip_file/tmp/clean_ips_$$.txt prepare_ip_list $IP_FILE $clean_ip_file local total_ips$(wc -l $clean_ip_file 2/dev/null || echo 0) if [ $total_ips -eq 0 ]; then echo 没有有效的IP地址 exit 1 fi echo 找到 $total_ips 个有效IP echo # 清空结果文件 $SUCCESS_FILE $FAILED_FILE echo 开始测试... echo ---------------------------------------- local tested0 success0 failed0 while read ip; do tested$((tested 1)) # 进度显示每10个或最后一个更新一次 if [ $((tested % 10)) -eq 0 ] || [ $tested -eq $total_ips ]; then printf \r 进度: %d/%d (%.1f%%) $tested $total_ips \ $(echo scale1; $tested*100/$total_ips | bc) fi if test_single_ip $ip; then echo $ip $SUCCESS_FILE success$((success 1)) else echo $ip $FAILED_FILE failed$((failed 1)) fi done $clean_ip_file echo echo ---------------------------------------- echo echo 测试报告 echo 总IP数: $total_ips echo 成功: $success echo 失败: $failed if [ $total_ips -gt 0 ]; then echo 成功率: $(echo scale2; $success*100/$total_ips | bc)% fi echo echo 结果文件: echo $SUCCESS_FILE echo $FAILED_FILE # 展示前5个成功的IP if [ -s $SUCCESS_FILE ]; then echo echo 成功IP示例前5个: head -5 $SUCCESS_FILE | cat -n fi # 清理临时文件 rm -f $clean_ip_file } main五、核心技术要点总结5.1 健壮的IP文件处理模板while IFS read -r line || [ -n $line ]; do line$(echo $line | tr -d \r) # 干掉Windows换行符 line$(echo $line | sed s/^[[:space:]]*//;s/[[:space:]]*$//) # 去首尾空白 [[ -z $line || $line ~ ^# ]] continue # 跳过空行和注释 ip$(echo $line | awk {print $1}) # 提取第一列 [[ $ip ~ ^[0-9]\.[0-9]\.[0-9]\.[0-9]$ ]] echo $ip done ipfile.txt5.2 安全稳定的SSH选项组合选项值作用ConnectTimeout3连接超时避免卡死StrictHostKeyCheckingno测试环境跳过主机密钥确认PasswordAuthenticationyes明确允许密码认证BatchModeno不禁用密码认证LogLevelERROR只输出错误减少干扰5.3 退出码判断参考timeout $TIMEOUT sshpass ... case $? in 0) echo 连接成功 ;; 124) echo 连接超时 ;; 5) echo 认证失败密码错误或账号被禁用 ;; *) echo 其他错误网络不通、端口关闭等 ;; esac六、运维经验沉淀经过这次实战总结了几条值得记住的原则原则说明小范围验证先用2-3个IP测试脚本逻辑确认无误再批量跑格式清洗前置任何外部来的文本文件先dos2unix再处理逐层加功能先跑通基础逻辑 → 加进度显示 → 加错误处理 → 加并行临时文件隔离并行场景下每个进程写入独立文件最后合并留下日志脚本执行时间、结果统计、异常信息都记录下来批量测试SSH连接表面上是技术问题本质上是对细节的把控编码格式、换行符、SSH选项、并发冲突任何一个细节没处理好整个流程就会卡住。好的自动化脚本不是一蹴而就的而是在不断遇到问题、解决问题的过程中逐渐打磨出来的。希望这篇踩坑实录能帮你避开我走过的弯路。