接到一个很有意思的活儿:用 Shell 批量配置网络设备、顺带把网络状态监控也一块儿做了。这标题乍看像是老网工的日常,但真要动起手来,里面全是细节和坑。我自己在项目里被几十台交换机挨个手工敲命令搞到崩溃之后,才老老实实把 Shell 脚本这套东西捡起来重新捋了一遍。今天这篇就把我实际用到的东西——从最基础的框架、到批量下发配置、再到状态监控和问题排查——完整复盘一遍。
先说一个基本判断:Shell 处理网络设备批量操作,真正好用的地方不是它功能多强,而是它“刚好够用”。网络设备大多支持 SSH 连接,Shell 天生就是和命令行打交道的,再加上系统自带,不需要额外安装重型依赖,在跳板机、运维堡垒机上几乎开箱即用。你当然可以用 Python + Paramiko 写一套更优雅的框架,但很多场景下,一个 50 行的 Shell 脚本就能搞定,没必要杀鸡用牛刀。
这篇文章适合谁?正在被“一台台设备手工刷配置”折磨的初级运维,想把手头重复性网络操作脚本化的工程师,以及准备在团队里推广自动化网络操作、但还没想好从哪个点切入的朋友。如果你已经用 Ansible 管理网络设备,这文里的思路也能帮你理解它底下那层逻辑是怎么转的。
1. 先想清楚:为什么是 Shell,而不是 Ansible 或者 Python
1.1 网络设备批量操作的真实痛点
先还原一下场景。假设你手头有 30 台接入层交换机,需要统一把 NTP 服务器地址改掉,或者统一开启 RSTP、调整链路聚合参数。如果一台一台登上去敲命令,按每台 5 分钟算,一个半小时就没了,这还不算中间被人打断、敲错命令、设备登录超时这些意外。
更麻烦的是误操作。手工模式下,你很难保证每台设备的输入完全一致。我那会儿就出过岔子:有两台交换机的 VLAN 配置被我手误敲错了,业务上线当天就出现二层环路,差点酿成事故。从那以后,凡是超过 5 台设备的统一变更,我坚决不再手工逐台操作。
批量操作的价值不只是省时间。它真正解决的是“一致性”——同样的配置,用同样的命令,在同一时间点下发,结果可预期,出错可回查。这些都是手工模式给不了的。
1.2 Shell 在“轻量批量”场景里的独特优势
Shell 这个选择,核心是“轻”。它不像 Ansible 那样需要额外的控制端环境、Python 依赖和设备端 Python 解释器,也不像 Python 脚本那样要考虑运行环境、第三方库、异常处理框架。你只要有一台 Linux 机器,装上 openssh-client,再加一个顺手的 expect 或 sshpass,就能开始干活。
我把日常批量运维拆分成了几个动作:连设备、发命令、收结果、做判断。这四个动作里,Shell 有天然的处理优势。比如批量循环设备列表,一个 for 循环就行;结果抓取后用 grep 和 awk 过滤关键字段,那更是 Shell 的看家本事;最后用重定向把结果落盘归档,不需要引入任何额外工具。
And 再一个关键点:Shell 脚本本身就是“所见即所得”的。发给设备的命令在脚本里写得很直白,评审的人一眼能看懂在干嘛。相比之下,用 Python 写一套封装好的 netmiko 调用,可读性反而不如纯 Shell 脚本直接。
1.3 边界意识:哪些场景不该硬上 Shell
Shell 不是万能的。我踩过几次坑之后,总结出几条“硬上会翻车”的场景:
- 需要同时操作几百台设备,且要处理大量并发回显时。Shell 的并发处理能力弱,xargs -P 能解决一部分,但输出交错、资源竞争会让人头疼。
- 需要解析设备返回的复杂结构化数据,比如用 SNMP 抓一张大表做关联分析。这是 Python 或专业网管系统的活儿。
- 需要在多厂商设备之间做复杂的命令差异转换。虽然可以通过函数封装适配,但配置多了会非常难维护。
所以我的选型原则是:10 到 60 台设备、操作内容相对单一、以配置下发生效为主的场景,Shell 就是最优解。 超过这个规模,或者追求更完善的重试、回滚机制,我可以随时换用 Python 重写同一套逻辑,Shell 版脚本本身就相当于需求文档和验证原型。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 批量操作前的准备工作:环境、账号与设备清单
2.1 最小化环境清单
动手写脚本之前,先把环境配好。我建议的最小环境是:
- Linux 操作系统的跳板机(我用的是 Ubuntu 22.04,但其实任何发行版都行)
- 安装 sshpass(非交互式密码认证用)或者 expect(更可控的交互式会话工具)
- 能连通所有目标网络设备的网络路由
- 一个可以存放脚本和日志的目录,比如 /opt/netops/
安装命令很简单,Ubuntu 上用 apt 装就行:
bash复制apt update
apt install -y sshpass expect openssh-client
如果公司安全策略比较严,不允许用 sshpass(因为它把密码暴露在命令行参数里,ps 能看到),那就用 expect。expect 的缺点是脚本语法看着费劲,但它不泄露密码,而且能处理复杂的交互场景。我通常两个都装,简单密码认证用 sshpass,遇到需要交互确认的用 expect。
2.2 账号和凭据处理:权限模型一定要提前想好
批量操作的核心风险点是账号权限。你不能用管理员账号直接操作每一台设备——那样一旦脚本出错,影响面直接拉满。
我的习惯做法是提前在网络设备上创建一个专用账号,权限只开放到本批操作需要的命令级别。比如这次只做 NTP 配置,那就只给这个账号对应的配置权限,不给全局管理权限。可以配合设备的命令授权机制去限制,华为、H3C、Cisco 都有类似的功能。
另外密码不要硬编码在脚本里。我踩过一个大坑:脚本里明文写了设备密码,结果脚本被同事传到内部 Wiki 页面,密码就那样裸奔了一周。后来我把密码放到脚本目录外的只读配置文件里,并且用环境变量方式传递:
bash复制export NET_DEV_PASSWORD='xxxxxx'
脚本里用 $NET_DEV_PASSWORD 引用,进程结束后环境变量自动消失,至少不会因为脚本文件外泄直接连密码一起泄了。
2.3 设备清单的整理:从 Excel 到纯文本的进化
批量脚本的第一步永远是“遍历设备”。设备清单文件看起来简单,但格式和字段设计直接影响后面所有逻辑。我推荐用纯文本 CSV 或者直接用空格分隔的文本,每行一台设备,字段包括:
code复制ip 主机名 厂商 型号 管理VLAN 备注
192.168.10.11 access-sw-01 huawei S5720 vlan100 一楼弱电间
192.168.10.12 access-sw-02 h3c S5130 vlan100 二楼弱电间
为什么不用 Excel?因为 Shell 处理 CSV 已经足够,Excel 还得额外转换编码、处理分隔符,反而多一道工序。而且文本文件可以直接用 grep、awk 做过滤,比如只对华为设备执行命令:
bash复制awk '$3=="huawei"{print $1}' devices.txt
这样一句就能拿到华为设备的 IP 列表,非常方便。我一般在项目里维护一个 device_archive 目录,按批次存放设备清单、配置备份、操作日志,文件名带上时间戳,后面排查时能快速定位“当时操作的是哪些设备”。
3. 核心环节:批量配置设备命令的下发与校验
3.1 最基础的批量下发框架:for 循环 + SSH
先看一个最简单的批量登录并执行命令的脚本骨架:
bash复制#!/bin/bash
# 批量收集设备信息
DEV_LIST="devices.txt"
LOG_DIR="./logs/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$LOG_DIR"
while read -r ip host vendor model vlan comment; do
# 跳过空行和注释行
[[ "$ip" =~ ^#.*$ || -z "$ip" ]] && continue
echo "开始处理: $host ($ip)"
sshpass -p "$NET_DEV_PASSWORD" ssh -o StrictHostKeyChecking=no \
-o ConnectTimeout=5 -o ConnectionAttempts=2 \
netadmin@"$ip" "display version" > "$LOG_DIR/${host}_version.txt" 2>&1
sleep 1
done < "$DEV_LIST"
这个脚本干的事很简单:逐行读取设备清单,对每台设备 SSH 过去执行 display version(华为命令),把结果存到以主机名命名的文件里。
这里有几个细节特别注意:
-o StrictHostKeyChecking=no是为了避免第一次连接时出现 host key 确认交互,否则脚本会卡死在 yes/no 提示上。-o ConnectTimeout=5设置连接超时,避免某台设备不可达时整个脚本长时间卡住。- 每台设备执行完
sleep 1,是给设备一点缓冲时间,同时也是避免瞬间并发连接过多被设备侧判定为异常访问。
这个基础框架能跑通之后,你做的所有增强都是在它基础上加功能。
3.2 适配多厂商设备:命令封装与差异处理
网络设备操作里最烦的就是厂商命令差异。同样查 VLAN 信息,华为用 display vlan,Cisco 用 show vlan,H3C 和华为大体一致但细节有区别。如果脚本里硬编码命令,换个厂商就得改脚本,维护成本很高。
我的做法是把设备操作命令封装成函数,按厂商分发:
bash复制get_vlan_info() {
local vendor="$1"
local ip="$2"
case "$vendor" in
huawei|h3c)
sshpass -p "$NET_DEV_PASSWORD" ssh ... netadmin@"$ip" "display vlan" ;;
cisco)
sshpass -p "$NET_DEV_PASSWORD" ssh ... netadmin@"$ip" "show vlan" ;;
*)
echo "Unsupported vendor: $vendor" ;;
esac
}
这样主流程只需要循环读取设备清单里的厂商字段,然后调用对应函数即可。新增厂商时,只需要增加一个 case 分支,不需要动主循环逻辑。
另一个经验:如果一批设备里厂商混杂,可以先按厂商分组,再逐组执行。这样做的好处是同一个操作命令在一个厂商组内保持一致,结果输出也好统一处理。我用 awk 按厂商列分组生成临时清单,然后逐组调用脚本:
bash复制awk -F' ' '{print > "dev_"$3".txt"}' devices.txt
for f in dev_*.txt; do
vendor="${f#dev_}"
vendor="${vendor%.txt}"
echo "===== 处理厂商: $vendor ====="
./batch_exec.sh "$f" "$vendor"
done
3.3 参数化配置模板:让同一套指令适配不同设备
批量配置最容易出问题的地方在于“同中有异”。比如 30 台交换机都要配置 NTP,但每台的管理 IP 不同、所属网段不同,甚至 NTP 服务器地址可能有两台。如果用死命令下发,每台设备都得单独写一条命令,等于又回到了手工模式。
解决思路是“生成配置 + 批量提交”。我先把配置项做成变量模板,然后用 sed 替换变量生成每台设备实际要执行的命令清单:
bash复制# template.cfg
system-view
ntp-server {NTP_SERVER_1} source-interface Vlanif{MGMT_VLAN}
ntp-server {NTP_SERVER_2} source-interface Vlanif{MGMT_VLAN}
return
save force
然后写一个循环,针对每台设备生成实际配置脚本:
bash复制while read -r ip host vendor vlan; do
sed -e "s/{NTP_SERVER_1}/ntp.aliyun.com/" \
-e "s/{NTP_SERVER_2}/ntp.tencent.com/" \
-e "s/{MGMT_VLAN}/$vlan/" template.cfg > "gen/${host}_ntp.cfg"
done < devices.txt
生成的配置文件里就是该设备真正要执行的内容。之后我再用 SSH 把配置片段推送到设备。整个过程分成“生成”和“下发”两步,好处是每台设备要执行的命令都有据可查,出了问题时可以回看生成的配置文件确认当时下发的是什么。
3.4 配置结果的确认与回滚机制
下发配置只是开始,真正重要的是确认“配置真的生效了”。我见过有同事脚本跑了半天,最后抽查发现三分之一设备没保存配置,一重启就全丢了。
所以我的脚本里,配置下发后至少要做三件事:确认、保存、核对。
- 确认:核对关键配置项是否已存在。比如配置 NTP 后,重新执行查看命令,grep 设备回显里是否有 NTP 服务器地址。
- 保存:执行设备的保存命令(华为/H3C 是
save force,Cisco 是write memory)。这里要额外注意有些设备保存时会交互确认,需要预先处理,我后面会在常见问题里专门讲。 - 核对:把设备回显里抓到的关键信息与预期值做比对,如果匹配,记录 OK,否则记录 FAIL 并保存原始输出到日志目录,方便人工排查。
这个比对逻辑也可以用 Shell 做得很轻量:
bash复制if grep -q "ntp-server $NTP_SERVER" "$LOG_DIR/${host}_ntp_check.txt"; then
echo "$host NTP配置确认正常"
else
echo "$host NTP配置可能未生效,请检查"
fi
一个批量任务执行完,我通常会生成一个汇总报告文件,内容大概长这样:
code复制==================== 批量配置执行报告 ====================
执行时间: 2025-01-15 22:30:00
总设备数: 32
成功: 30
失败: 2
失败设备: access-sw-07 (连接超时), access-sw-19 (保存失败)
=========================================================
有了这个报告,我就能快速定位哪些设备需要人工介入,不用一台台翻日志。
4. 网络状态监控:从丢包检测到设备资源采集
4.1 基于 Ping 的连通性监控
批量配置做完之后,接下来是持续监控网络状态。最简单也最有效的监控方式是 Ping 探测。Shell 里做这个非常顺手,只需要一个循环加一个判断:
bash复制ping_monitor() {
local ip="$1"
if ping -c 3 -W 2 "$ip" > /dev/null 2>&1; then
echo "OK"
else
echo "FAIL"
fi
}
但要注意,ping 的退出码在部分系统上表现不一样,比如超时和主机不可达有时会混在一起。更稳妥的做法是解析 ping 的输出,提取丢包率和平均延迟:
bash复制ping_result=$(ping -c 5 -W 2 "$ip" | tail -1)
# 输出示例: 5 packets transmitted, 5 received, 0% packet loss, time 4006ms
loss=$(echo "$ping_result" | grep -oP '\d+(?=% packet loss)')
这个 grep -oP 用到了 Perl 正则,抓取 % 前面的数字就是丢包率。如果丢包率高于阈值,说明链路质量有问题,需要进一步排查。
4.2 设备层面的状态采集:CPU、内存、端口
Ping 只是“通不通”的问题,真正判断网络健康还要看设备自身状态。网络设备的 CPU 利用率和内存使用率很高时,即使链路通,业务也可能卡顿甚至断流。
SSH 登录设备采集状态的方式,和我们前面批量下发命令是同一个套路。华为设备查 CPU:
bash复制display cpu-usage
查询端口状态:
bash复制display interface brief
采集回来的文本里面是原始输出,还需要解析成结构化数据。比如我要监控所有接入交换机上哪些端口 Down 了,把回显存到文件后,用 grep 过滤状态字段:
bash复制grep -E "GE|Eth" "$LOG_DIR/${host}_iface.txt" | awk '$2=="down"{print $1, $2}'
这里 awk 的作用是按空格分段取字段。设备回显格式可能因为型号不同有差异,第一次写解析脚本时,我建议先手工执行一条命令看看输出长什么样,再对着写匹配规则,不要闭着眼写。
4.3 监控结果持久化与趋势判断
单纯的“采集一次看一眼”意义不大,监控要能积累数据、看到趋势。我习惯的做法是把采集到的数据追加到一个以日期命名的 CSV 文件里:
bash复制echo "$(date +%Y-%m-%d_%H:%M:%S),$host,$cpu_usage,$mem_usage" >> "monitor/history_$(date +%Y%m).csv"
这样积累一个月之后,就能用简单的命令分析趋势。比如查某台设备某个时间段 CPU 是否持续偏高:
bash复制grep "access-sw-01" monitor/history_202501.csv | awk -F',' '$3>80{print $1, $3}'
拿到这些数据后,配合一个定时任务(cron)每天循环跑一遍,就能实现最基础的持续监控。我把这个思路叫作“用 Shell 搭一个极简可观测性平台”——它不够花哨,但数据扎实、可控、完全透明。
4.4 用定时任务把监控跑起来
手工执行监控脚本是反人类的,必须让定时任务自动跑。我通常写一个总控脚本,里面串联三个动作:批量采集设备状态、解析关键字段、追加写入监控数据文件。然后在 crontab 里配置:
bash复制*/10 * * * * /opt/netops/collect_status.sh >> /opt/netops/logs/cron.log 2>&1
每 10 分钟跑一次,采集设备状态。自动任务跑了一段时间后,我会定期检查日志文件,确保没有脚本异常退出的情况。如果发现某台设备连续多次采集异常,就把它单独摘出来重点排查。
5. 遇到过的坑与排查技巧实录
5.1 设备回显乱码和编码问题
真实网络设备上,中文注释、中文欢迎语都可能变成乱码。这通常不是脚本的 bug,而是终端编码和设备默认编码不一致。解决办法是 SSH 连接时强制指定终端类型和编码,比如在 ssh 命令前加环境变量:
bash复制export LC_ALL=en_US.UTF-8
或者用 -o SendEnv=LC_ALL 把本地环境变量传给设备端。如果设备端不支持 UTF-8,那就统一让设备输出英文界面,在设备端设置 language-mode English(华为设备)之类。
我之前就因为没有处理编码,导致脚本里 grep 中文字段总是匹配不上,白白排查了半个多小时。
5.2 设备保存配置时的交互确认坑
华为和 H3C 设备上执行 save 或 save force 时,部分旧版本会提示确认。比如:
code复制Are you sure to continue? [Y/N]:
如果你的脚本是直接执行命令然后断开连接,这个提示会一直挂着,配置根本没保存成功。为了避免这个问题,我一般在 SSH 命令里用键盘输入重定向,把 “y” 预先送给设备:
bash复制echo "y" | sshpass -p "$PASSWORD" ssh netadmin@"$ip" "save force"
或者在设备命令里直接带上完全确认参数,华为的 save force 本身已经不交互了,但 H3C 老版本可能不行,所以最稳妥的是脚本里做一个兜底:执行完保存命令后,sleep 一秒钟再断开连接,并把保存结果一并抓回来。
5.3 SSH 连接数过多导致设备拒绝连接
设备对同时建立的 SSH 会话数通常有限制,默认可能只有 5 到 10 个。如果脚本不加控制地快速并发 SSH 到同一台设备,或者一批设备同时连接,很容易触发设备端限制,报错后直接拒绝连接。
我的应对措施:一是脚本里加 sleep 1 甚至随机延迟,把连接打散;二是用 xargs -P 控制并发数,比如同时最多跑 4 个 SSH 会话:
bash复制cat devices.txt | xargs -P 4 -I {} ./execute_one.sh {}
这两个措施配合下来,基本不会再遇到连接被拒绝的问题。
5.4 Shell 脚本里常见的基础坑
- 变量名不要和系统环境变量冲突。我试过在脚本里用
PATH存路径,结果脚本跑到一半所有命令都找不到了。 while read循环里执行 ssh 时,注意 ssh 会吃掉标准输入。我在前面给的框架里就是踩过这个坑——read从文件读第一行,SSH 把剩下的行全吃掉了,循环只跑了一次。- 解决方法是给 ssh 加上
-n参数,或者把设备清单重定向到其他文件描述符:
bash复制while read ...; do
ssh -n ... "$ip" "command"
done < devices.txt
最后再分享一个我个人的习惯:任何批量操作脚本在真正跑全部设备之前,我一定先抽一台设备做“单机演练”,确认命令格式、回显解析、保存逻辑都正常,再放到大批量里跑。这个习惯帮我挡住了至少三次可能的线上事故。
Shell 这套东西,说白了就是“用简单的工具,干朴实的活儿”。它不像大型自动化平台那样有漂亮的 Web 界面和丰富的数据报表,但当你需要在深夜把几十台设备的统一配置一次搞定,或者在业务报障时快速扫一遍全网的连通性和负载情况,一个写得干净的 Shell 脚本往往是最高效、最可靠的选择。希望上面这些经验能让你少走一些我走过的弯路。
