
搞网络这行谁还没被“备份交换机配置”这事儿折腾过。以前在机房里守着Console口一台一台敲命令遇上设备多或者链路不稳半天就搭进去了。后来换成了Telnet/SSH脚本批量跑确实省事不少但问题是得在固定的办公环境、固定的网络里执行脚本和清单都躺在公司电脑上。直到有一次出差路上客户临时打电话说现场要加几台设备让我远程把配置备一份过去我才意识到能不能把这套东西做到“自驾级别”——也就是在任何地方、任何时间只要笔记本在身边、网络连通就能把一整批华三交换机的配置干干净净地捞回来。这次分享的就是我自己一直在用的批量备份华三交换机配置的Shell脚本。它解决的核心痛点很明确多设备、多账号、密码不统一、网络忽好忽坏、还得防着配置备份不全或者备份文件损坏。经过多轮迭代之后这个脚本已经足够“自驾”——不依赖特定的堡垒机、不依赖特定的网管平台但能稳定干活识别异常输出可读性很强的日志。1. 方案思路与选型1.1 为什么选Shell而不是Python或者网管软件先说选型。这个问题我纠结过一阵儿后来根据实际场景定了Shell。原因其实不复杂有三点。第一现场环境不可控。你开着车到一个客户现场或者窝在酒店改配置借用的往往不是自己的电脑。对方电脑上可能装了Python也可能没装装了的版本还五花八门。但只要你用的是Linux或者macOSShell天然存在不需要额外安装解释器。Windows的话Git Bash、WSL也都自带标准Shell环境基本零依赖。第二脚本要能挂到crontab里无人值守跑。生产环境里的备份任务多数是想让它按周或者按天自动跑备份完成之后发个邮件或者留个日志。Shell脚本做定时任务、做日志切割、做文件归档这些事天生顺手。第三华三交换机的命令行接口对脚本非常友好。它本身就是类Unix的命令行风格显示配置用display current-configuration退出用quit和return。只要能在脚本里模拟一个终端会话把命令逐条喂进去再用expect匹配回显判断是否成功就能完成备份。这活儿Shell配合expect干起来最直接。当然如果你就是要一个漂亮的Web界面或者想集成到CMDB自动化平台里做配置漂移检测那肯定是用Python写更合适比如Paramiko、Netmiko都是很强的库。但我这个场景是“自驾”强调的是随时随地能开跑没必要背一个庞大的运行时。1.2 备份机制整体设计整个备份脚本的核心其实是解决“如何稳定地远程登录设备并抓取完整配置”的问题。我设计的第一版方案很简单写一个for循环读IP列表逐台Telnet/SSH登录后执行display current-configuration把输出重定向到文件。但这版踩了不少坑主要包括网络波动导致登录时卡死设备密码偶尔有变化导致登录失败卡在密码提示符display current-configuration的输出特别长终端宽度不够时会被截断大段输出过程中偶尔会蹦出---- More ----分页符直接把后续输出卡住一台失败后脚本不退出但也无法准确判定它成功了没有。所以后来在机制上做了几个关键优化用expect来处理交互而不是简单的管道。因为交换机需要交互式登录单纯的ssh userhost没法判断是否登录成功。每条命令之间用sleep短眠并且用expect匹配设备名这样的提示符作为命令执行完毕的标志确保一条命令完整执行后再发下一条。专治分页符。发送screen-length disable临时关闭分页或者执行display current-configuration之前先设置终端长度不限制。这是备份完整性的关键。每台设备备份完成后立即生成文件大小记录配合日志输出下次回头检查时一眼就能看出哪台设备备份异常。支持多线程/并发避免设备一多就串行排队等到天荒地老。这套机制稳定之后不管是几台设备还是上百台设备都能快速批量拉取而且可以放心丢给定时任务跑。2. 脚本核心实现2.1 文件清单与目录结构脚本我分成三个文件switch_backup/ ├── backup_switch.sh # 主备份脚本 ├── ip_list.txt # 设备IP清单一行一个可写备注 └── logs/ # 日志目录自动创建 ├── backup_YYYYMMDD.log # 当次运行日志 └── configs/ # 配置备份文件存放处 ├── 192.168.1.1_20250410.cfg └── 192.168.1.2_20250410.cfgIP清单的格式很简单192.168.1.1 admin password123 192.168.1.2 admin password456 192.168.1.3 root adminswitch每一行三个字段IP地址、用户名、密码。如果账号密码都一样也可以在脚本里统一配置IP清单里只写地址即可。实际情况中设备密码经常不是统一的尤其接手别人维护过的网络时所以我把三个字段做成可选的脚本里优先取IP清单后两列如果没有就使用默认账号密码。2.2 用expect处理登录与会话expect是整个脚本的灵魂。它做的事情本质上就是一个“人工接线员”把命令发给交换机然后等待交换机回显的特定字符串匹配到了再发下一条。登录环节我写了两个分支一个是Telnet另一个是SSH。login_device() { local ip$1 local user$2 local pass$3 # 优先用SSH如果端口不通再降级到Telnet if ssh_check $ip; then expect EOF set timeout 20 spawn ssh -o StrictHostKeyCheckingno -o UserKnownHostsFile/dev/null $user$ip expect { *password* { send $pass\r } *yes/no* { send yes\r exp_continue } timeout { exit 1 } } expect { *#* { send screen-length disable\r } ** { send screen-length disable\r } timeout { exit 1 } } expect { *#* { send display current-configuration\r } ** { send display current-configuration\r } timeout { exit 1 } } EOF else expect EOF set timeout 20 spawn telnet $ip expect { *Username:* { send $user\r } *login:* { send $user\r } timeout { exit 1 } } expect { *Password:* { send $pass\r } timeout { exit 1 } } expect { *#* { send screen-length disable\r } ** { send screen-length disable\r } timeout { exit 1 } } expect { *#* { send display current-configuration\r } ** { send display current-configuration\r } timeout { exit 1 } } EOF fi }这里最关键的一步是screen-length disable作用是关闭交换机当前会话的分页显示。如果不做这一步当配置超过一屏时交换机会输出---- More ----等待你按空格键翻页脚本在expect会话里不会自动翻页后面的配置就永远读不到了。这一点我吃过亏早期备份出来的文件总是只有前面一部分排查了半天才发现是分页符闹的。2.3 并发执行与流程控制如果设备数量少串行慢慢跑也就罢了。但如果要备份三四十台以上的设备一台接一台平均每台30秒一轮下来就是二十多分钟。为了压缩整体耗时我用了一个简单的并发方案把每个IP的备份任务放到后台执行最后再统一wait。backup_all() { rm -rf $BACKUP_DIR mkdir -p $BACKUP_DIR local task_count0 while read -r line; do [ -z $line ] continue case $line in \#*) continue ;; esac ip$(echo $line | awk {print $1}) user$(echo $line | awk {print $2}) pass$(echo $line | awk {print $3}) backup_single $ip $user $pass task_count$((task_count 1)) # 控制并发数避免一次拉起太多进程 if [ $task_count -ge $MAX_CONCURRENT ]; then wait task_count0 fi done $IP_LIST wait }这里我设置了一个并发上限默认5个进程同时跑。原因是如果并发数太高一来笔记本资源容易吃紧二来部分老交换机并发连接数本身有限连接一多就会拒绝服务。每个子任务执行完会在日志里打一行结果比如[2025-04-10 14:32:11] 192.168.1.1 备份成功, 文件大小: 24KB, 耗时: 18s [2025-04-10 14:32:15] 192.168.1.2 备份成功, 文件大小: 31KB, 耗时: 22s [2025-04-10 14:32:19] 192.168.1.3 备份失败, 账号密码错误这样即使几十台设备并发跑回来翻日志也一目了然。3. 完整脚本与使用指南3.1 可用的核心脚本把核心逻辑串在一起去掉多余注释整份脚本大致长这样#!/bin/bash # # 华三交换机配置批量备份脚本 # 支持SSH/Telnet自动登录,并发备份,日志输出 # IP_LISTip_list.txt BACKUP_ROOT$(pwd)/backup LOG_FILE$(pwd)/backup_$(date %Y%m%d_%H%M%S).log MAX_CONCURRENT5 DEFAULT_USERadmin DEFAULT_PASSadmin123 mkdir -p $BACKUP_ROOT log() { echo [$(date %Y-%m-%d %H:%M:%S)] $1 | tee -a $LOG_FILE } backup_single() { local ip$1 local user$2 local pass$3 local filename${ip}_$(date %Y%m%d_%H%M%S).cfg local dest$BACKUP_ROOT/$filename # 记录开始时间 local start_time$(date %s) # 检测SSH端口是否开启,22端口不通则降级到Telnet if timeout 3 bash -c echo /dev/tcp/$ip/22 2/dev/null; then expect EOF $dest 2/dev/null set timeout 30 spawn ssh -o StrictHostKeyCheckingno -o UserKnownHostsFile/dev/null $user$ip expect { *password:* { send $pass\r } *yes/no* { send yes\r exp_continue } timeout { exit 1 } } expect { *#* { send screen-length disable\r } ** { send screen-length disable\r } timeout { exit 1 } } expect { *#* { send display current-configuration\r } ** { send display current-configuration\r } timeout { exit 1 } } expect { *#* { send quit\r } ** { send quit\r } timeout { exit 1 } } expect eof EOF else expect EOF $dest 2/dev/null set timeout 30 spawn telnet $ip expect { *Username:* { send $user\r } *login:* { send $user\r } timeout { exit 1 } } expect { *Password:* { send $pass\r } timeout { exit 1 } } expect { *#* { send screen-length disable\r } ** { send screen-length disable\r } timeout { exit 1 } } expect { *#* { send display current-configuration\r } ** { send display current-configuration\r } timeout { exit 1 } } expect { *#* { send quit\r } ** { send quit\r } timeout { exit 1 } } expect eof EOF fi local end_time$(date %s) local elapsed$((end_time - start_time)) # 备份成功的判断标准:文件存在且大小大于0 if [ -s $dest ] grep -q version $dest 2/dev/null; then local size$(du -h $dest | awk {print $1}) log $ip 备份成功, 文件大小: $size, 耗时: ${elapsed}s else log $ip 备份失败, 请检查账号密码或网络连通性 rm -f $dest fi } # 主流程 echo echo 华三交换机批量备份脚本 echo 开始时间: $(date %Y-%m-%d %H:%M:%S) echo total_count0 success_count0 fail_count0 task_count0 while read -r line; do # 跳过空行和注释行 [ -z $line ] continue case $line in \#*) continue ;; esac ip$(echo $line | awk {print $1}) user$(echo $line | awk {print $2}) pass$(echo $line | awk {print $3}) # 如果清单里没写用户名密码,使用默认值 [ -z $user ] user$DEFAULT_USER [ -z $pass ] pass$DEFAULT_PASS total_count$((total_count 1)) backup_single $ip $user $pass task_count$((task_count 1)) if [ $task_count -ge $MAX_CONCURRENT ]; then wait task_count0 fi done $IP_LIST wait log 全部备份任务执行完毕, 总计: $total_count 台3.2 使用步骤实际使用的步骤非常简短# 1. 给脚本加执行权限 chmod x backup_switch.sh # 2. 编辑IP清单 vim ip_list.txt # 3. 执行备份 ./backup_switch.sh如果你在Windows环境装了Git Bash之后这个脚本同样能跑。唯一要注意的是Windows里没有自带Telnet命令如果是纯Windows环境建议直接用SSH方式早期版本华三设备如果只开了Telnet就要在Windows功能里把Telnet客户端打开。3.3 备份文件内容示例正常的备份文件开头是这个样子的# version 7.1.064, Release 6605P01 # sysname SW-ACCESS-01 # clock timezone Beijing add 08:00:00 #后面跟完整的接口配置、VLAN配置、路由配置等等。如果备份出来的文件只有交互回显、没有实际的配置内容多半是在expect匹配的登录提示符上出了问题。有些华三设备版本较老登录后的提示符不是常见的H3C或者[H3C]而是设备名比如SW-ACCESS-01这时expect里匹配*#*没问题**也没问题因为华三用户视图的提示符通常是设备名系统视图是[设备名]退出系统视图后回到设备名所以一般能匹配到。但有一种情况需要特别留意如果设备名本身包含特殊字符或者登录前路由器已经处于系统视图比如有人没退出提示符就是[设备名]这时候匹配*#*是匹配不到的需要额外加一层*]*的匹配。我把这个边界情况也考虑进去了实际脚本里可以加一行expect { *#* { send screen-length disable\r } ** { send screen-length disable\r } *]* { send return\r } timeout { exit 1 } }也就是发现不在用户视图就先return退回用户视图再执行后续命令。4. 使用过程中的问题与排查4.1 常见问题速查表经过几个月的实践我把常见问题整理成了下面这张表。现象可能原因解决方法登录后卡住不动交换机提示符与expect匹配串不一致手动登录设备看提示符调整匹配规则备份出的文件为空命令没发出去或者回显没被捕获检查expect的timeout延长到30秒以上备份出的文件只有半截分页没关闭确认是否成功发送screen-length disable有些设备超时失败密码错误/用户名错误/权限不足单独登录验证确认后更新ip_listSSH连接被拒绝设备没开SSH服务降级改走Telnet或先在设备上开启SSH服务文件里全是乱码终端编码问题在expect脚本中设置编码或使用二进制模式捕获备份速度奇慢换了网络环境延迟较大加大timeout值减少并发数同一台设备备份的配置和上一次差异很大有配置被人改动过检查日志中的备份时间尽量安排在变更窗口之外4.2 关键排障经验和细节第一个要提醒的是编码问题。华三设备默认编码一般是GB2312或者默认ASCII中文描述出现在配置里的时文件如果存成UTF-8打开看就是乱码。我的办法是备份完统一用iconv转码或者直接用二进制方式保存。iconv -f GBK -t UTF-8 源文件 新文件多数情况下配置里的中文只是description字段乱码不影响配置恢复但你要是想拿去做配置比对或合规审计建议还是处理一下。第二个问题是有些华三设备的老系统对并发SSH连接数有限制每台设备大概允许4-5个会话。如果脚本并发数设置过大设备会拒绝新连接。所以并发数这里我建议不要超过5尤其当你备份的是一整批老旧设备时。第三个是文件积累问题。备份时间一长backup/目录里文件越来越多。我后来加了一个简单的清理逻辑默认只保留最近30次备份的文件更早的自动压缩归档。# 保留最近30份备份文件 ls -1t $BACKUP_ROOT/*.cfg | tail -n 31 | xargs rm -f4.3 一个真实案例我朋友公司有一批华三S5130分布在总部和几个分公司跨地域组网有时候分公司那边的网络不稳。他第一次用这个脚本时非常狼狈二十台设备里只有五台备份成功了。后来排查发现原因有两个一个是他IP清单里好几台设备的密码已经被前任网管改过了清单上还是老密码。所以第一次跑全是密码错误。另一个是跨地域传输延迟高expect的timeout设得只有10秒配置输出还没传完expect就以为出问题直接退出了导致文件残缺。后来我把timeout统一调到30秒并加了重试机制问题就解决了。重试逻辑也分享一下# 单台设备最多重试3次 retry_times3 while [ $retry_times -gt 0 ]; do backup_single $ip $user $pass if [ -s $dest ]; then break fi retry_times$((retry_times - 1)) log $ip 重试, 剩余次数: $retry_times sleep 5 done不过重试要慎用。比如密码错误这种确定性错误重试几次都是同样的结果白白浪费时间还在设备日志里留下大量认证失败的记录。我的建议是如果是网络超时导致的失败重试有效如果是账号密码错误或者权限不足直接跳过等人工处理。4.4 备份文件校验的做法备份完的文件最好做个校验不然备份了一堆废文件自己还不知道。我现在的做法是备份完成后跑一个检查脚本做三件事。第一判断文件大小小于1KB的文件大概率是异常备份需要标记出来。第二检查文件的头几行是否包含version字段。华三设备的配置文件开头几行一定会有系统版本信息。如果连version都没有说明备份流程大概率没走完。第三对每台设备的备份文件做MD5校验保存成一个checksum.md5文件。这样即使后面文件被意外修改或者移动过也能快速比对出完整性问题。md5sum backup/*.cfg checksum.md5也可以反向操作比对前后两次备份的MD5如果差异过大就要留意是不是有配置被大范围改动了。5. 后续优化与扩展方向5.1 增加压缩归档备份文件是纯文本压缩率非常高。一台交换机的配置大概20-40KB一百台设备也就几个MB但日积月累下来数量还是可观的。我现在在脚本里加了一个小功能每周日凌晨会把当周备份目录整体打成一个tar.gz包然后传到存储服务器上。tar -czf backup_$(date %Y%m%d).tar.gz backup/如果公司有NAS或者对象存储也可以加一个上传动作让备份在异地上再做一份。所谓“自驾”除了笔记本上能跑更要让数据跟着你走别到时候车在服务区备份文件在地球的另一端。5.2 备份后配置漂移检测这一块我目前还在做但觉得很值得分享。配置备份不只是为了“坏了能恢复”更重要的作用是检测配置漂移——也就是看看有没有人不经流程改了配置。做法很简单拿最新的备份和上一次的备份做diff如果有差异就把差异部分输出到一个diff_YYYYMMDD.txt文件里然后邮件或者钉钉通知。脚本里加一个函数就能实现check_diff() { local ip$1 local new_file$2 local old_file$(ls -t $BACKUP_ROOT/${ip}_*.cfg 2/dev/null | sed -n 2p) if [ -n $old_file ] [ -f $old_file ]; then local diff_output$(diff $old_file $new_file) if [ -n $diff_output ]; then log $ip 配置发生变化 echo $diff_output $DIFF_DIR/${ip}_diff.txt fi fi }如果觉得diff太粗糙也可以只比对关键配置块比如接口VLAN和ACL规则。但作为起步全量diff已经足够发现问题。5.3 定时任务与告警联动把脚本挂到crontab里每周一到周五早上七点自动跑跑完把日志输出到固定文件如果失败超过一定数量就通过企业微信机器人或者邮件发告警。0 7 * * 1-5 /opt/scripts/switch_backup/backup_switch.sh /opt/scripts/switch_backup/backup_cron.log 21这里有一个细节crontab环境里的PATH和手动执行时不一样expect的路径要写成绝对路径不然定时任务执行时可能因为找不到expect导致脚本全部失败。我就在这上面踩过坑手动执行一切正常一挂到crontab里就全部报expect: command not found。后来在脚本开头加了export PATH/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin这样就稳定了。5.4 配合批量配置下发备份的逆向操作是批量配置下发。早期华三设备配置敏感不能直接批量下发但可以生成一个标准化的配置片段通过脚本往每台设备的相应视图中灌入。比如批量修改SNMP只读团体字、批量下发NTP服务器地址这类操作频率不高但遇到搬迁或者合规整改时很重要。思路类似但要注意的是下发配置比备份的风险高很多。备份错了最多是文件不对下发错了那可是直接把线上设备改坏了。所以我的建议是下发前必须逐台备份当前配置下发后立即执行一次display current-configuration把结果和备份对比每一步都要打印明确日志设备清单里标注好设备型号和版本避免用错命令集。写在最后这个脚本一开始只是我随手写的十几个expect命令后来不断从实际使用中发现问题、调整细节才变成现在这种“自驾级别”的稳定工具。对我个人来说最深的体会是脚本本身只是个壳真正值钱的部分是对设备行为、网络边界条件和异常处理的判断。每次被一个卡住的登录、一个被截断的输出坑过之后把对应逻辑补进去工具就会越来越顺手。如果你也是整天跟交换机打交道的人建议照着这份思路自己搭一套。也不需要一开始就追求功能齐全先跑通三台设备再扩到三十台再慢慢加并发、加重试、加告警你会越来越清楚地知道哪些功能是刚需哪些只是锦上添花。备份这件事平时没人关注但真到出故障的那一刻它就是你手里最大的底牌。