中文编程实操知识库:聚焦系统脚本自动化与最后一公里问题解决
1. 项目概述:一个专注编程实践的中文技术站点到底在解决什么问题
“WizardWu 編程網”——这个名字乍看像个人博客,但细究其域名结构、内容沉淀与长期更新节奏,它实际是一个以中文为母语的编程实操型知识库。它不主打算法竞赛、不追逐AI大模型热点、不堆砌概念术语,而是把重心牢牢钉在“写得出来、跑得起来、改得明白”这九个字上。我从2018年起持续关注这个站点,跟踪它发布的近320篇原创文章,发现其核心关键词高度集中:Python 脚本自动化、Linux 系统管理脚本、Windows 批处理与 PowerShell 深度应用、Web 前端轻量交互(HTML+JS)、嵌入式 Linux(OpenWrt)下的 Shell 工具链开发。这些不是宽泛的技术方向,而是具体到“如何用一行 sed 替换 OpenWrt 配置文件中的 IP 地址段”“怎样让 Python 脚本在 Windows 服务模式下稳定运行十年不崩溃”“为什么 crontab 里执行的 Python 脚本总提示找不到模块,而命令行却正常”的颗粒度。
它解决的,是大量一线运维工程师、嵌入式固件开发者、中小型企业 IT 支持人员每天真实面对的“最后一公里问题”:文档里查不到的环境差异、手册没写的权限陷阱、Stack Overflow 上高票答案在你机器上就是不生效的玄学报错。这类用户往往没有专职 DevOps 团队兜底,也没有时间重学整套理论体系,他们需要的是:可复制的完整命令、带注释的最小可运行代码、明确标注了测试环境(Ubuntu 22.04 / OpenWrt 21.02 / Windows Server 2019)的实操记录、以及最关键的一条——失败时该看哪几行日志、改哪两个配置项、重启哪个服务。WizardWu 的价值,正在于他把自己踩过的每一个坑,都转化成了带时间戳、带系统版本号、带错误截图(文字描述版)的“防踩指南”。这不是教科书,这是贴在机房服务器机柜侧面、被咖啡渍浸染过三次的便签纸。
2. 内容整体设计与思路拆解:为什么选择“窄而深”而非“广而全”
2.1 核心定位:做“系统级脚本工程师”的同行者,而非“全栈工程师”的导师
WizardWu 編程網的内容架构,本质上是一张以操作系统为横轴、以脚本语言为纵轴的二维矩阵图。横轴覆盖:Linux(Debian/Ubuntu/CentOS/OpenWrt)、Windows(Server/Desktop)、macOS(少量);纵轴聚焦:Shell(Bash/Zsh)、Python(3.6–3.11)、PowerShell、批处理(.bat)、HTML+JavaScript(仅限本地 HTML 文件交互,无后端)。它刻意避开了 Node.js 全栈框架、React/Vue 大型 SPA、Docker/K8s 编排等当前热门但对目标用户“非刚需”的领域。这种取舍背后有非常现实的工程逻辑:
维护成本可控:一个作者独立运营,若同时覆盖 Web 前后端、云原生、移动端,内容质量必然稀释。WizardWu 将全部精力投入在“脚本如何与操作系统内核、进程管理器、文件系统、网络栈直接对话”这一层,确保每篇文章的命令都能在读者同版本系统上 1:1 复现。
问题复现率极高:Linux 下
cron环境变量缺失导致脚本失败、Windows 服务账户权限不足无法访问网络共享、OpenWrt 中opkg安装包依赖冲突——这些问题在千万台设备上重复发生,但官方文档永远只写“应该怎么做”,不写“为什么这么做会失败”。WizardWu 的文章标题常是《在 Ubuntu 20.04 的 crontab 中运行 Python 脚本失败的 7 种原因及逐条验证方法》,这种“问题驱动”的结构,直击用户打开浏览器搜索时的真实意图。知识迁移路径清晰:掌握 Bash 的
set -euxo pipefail严格模式、Python 的subprocess.run(..., check=True)错误捕获、PowerShell 的Start-Process -Wait -NoNewWindow后台静默执行,这些技能点看似零散,实则共同构成“可靠自动化”的底层能力。用户从一篇 OpenWrt 的uci配置脚本入门,很快就能迁移到写 Windows 的域策略批量部署脚本,因为核心思维一致:识别系统接口 → 构建幂等操作 → 设计失败回滚 → 记录可审计日志。
2.2 内容组织逻辑:按“场景-工具-陷阱”三层嵌套,拒绝知识碎片化
站点文章不按语言分类(如“Python 教程”),也不按系统分类(如“Linux 技巧”),而是严格遵循“一个真实工作场景 + 一种最简工具链 + 三个典型陷阱”的三段式结构。例如一篇关于“自动备份路由器配置”的文章,其骨架是:
- 场景锚定:“公司有 47 台 OpenWrt 路由器,需每日凌晨 2 点自动备份配置到 NAS,要求失败时邮件告警,且备份文件名含设备 MAC 和时间戳”;
- 工具链锁定:“仅使用 OpenWrt 自带的
curl、date、sha256sum和 NAS 提供的 SMB 共享,不安装额外软件”; - 陷阱显性化:“陷阱1:OpenWrt 默认 busybox
date不支持%N纳秒,导致同一秒内多台设备备份文件名冲突;陷阱2:SMB 挂载点在crond环境下不可见,需在脚本开头显式mount;陷阱3:curl上传超时未设-m 30,导致 cron 卡死进程”。
这种结构强迫作者剥离所有“可能有用但非必需”的信息,把读者注意力牢牢锁在“此刻要解决的具体任务”上。它不教curl的全部参数,只教curl -f -s -S -m 30 -X PUT --data-binary "@$backup_file" "smb://nas/backup/$mac_$(date +%Y%m%d_%H%M).cfg"这一行中每个 flag 的真实作用——-f是让 HTTP 非2xx状态码直接报错退出(触发后续告警),-s -S是隐藏进度条但显示错误(便于日志分析),-m 30是防止 NAS 响应慢导致 cron 任务堆积。每一处取舍,都是对一线工程师时间成本的尊重。
2.3 技术选型哲学:拥抱“陈旧但稳定”的工具链,拒绝为新而新
WizardWu 对工具的选择,体现了一种近乎偏执的稳定性优先原则。他极少使用 Python 的asyncio(理由:调试困难,错误堆栈不直观),几乎不用pipx(理由:增加一层抽象,pip install --user更透明);在 Windows 环境下,他坚持用.bat+PowerShell混合脚本而非纯 PowerShell(理由:.bat启动快,能绕过 PowerShell 执行策略限制,适合做入口门面);对于日志,他推荐logger命令写入 syslog,而非 Python 的logging模块(理由:logger输出格式统一,journalctl -u myscript可直接过滤,无需额外解析)。这种选择不是技术保守,而是基于对生产环境复杂性的深刻理解:
可预测性即可靠性:
bash的$(...)命令替换、awk '{print $1}'字段提取、grep -q静默检查,这些 POSIX 兼容语法在 1995 年的 Solaris 和 2024 年的 OpenWrt 上行为完全一致。而 Python 的pathlib在 3.4 引入,3.6 增加Path.home(),不同版本间细微差异足以让脚本在客户现场崩溃。故障隔离简单:当一个
.bat脚本调用powershell.exe -ExecutionPolicy Bypass -File deploy.ps1失败时,只需看.bat的ERRORLEVEL和deploy.ps1的Write-Error输出,两层边界清晰。若全用 PowerShell,try/catch嵌套过深时,一个Invoke-RestMethod的 TLS 版本协商失败,可能被外层catch吞掉关键错误码。学习曲线平缓:对刚接触自动化的新手,让他先写
for /f "tokens=1,2 delims=:" %%a in ('ipconfig ^| findstr "IPv4"') do @echo %%b理解 Windows 批处理的管道和循环,远比直接扔给他一个Ansible PlaybookYAML 文件更有效。WizardWu 的文章里,所有“高级技巧”都建立在扎实的“基础命令组合”之上,比如用find /c ":" < file.txt统计行数(比wc -l更兼容老旧 Windows 系统),这种细节恰恰是老手才懂的生存智慧。
3. 核心细节解析与实操要点:从一篇文章看透其内容构建方法论
3.1 文章结构解剖:以《在 Windows Server 2016 上将 Python 脚本注册为服务并实现自动重启》为例
这篇文章是 WizardWu 站点的典型样本,全文 2800 余字,无一张图片,纯文字+代码块构成。我们拆解其骨架,看它是如何把一个常见需求转化为可落地方案的:
引言(约300字):不讲 Python 服务化原理,开门见山说“上周帮客户部署监控脚本,要求开机自启、崩溃自拉起、日志写入 Windows 事件查看器。试了 NSSM、WinSW、sc create 三种方式,最终选 sc,因为 NSSM 的 GUI 配置在 Server Core 版本不可用,WinSW 的 XML 配置易出错,而 sc 是系统自带,命令行一条搞定”。——用失败案例建立信任,用版本限定(Server Core)锚定场景,用工具对比说明选型依据。
环境声明(强制区块):
> 提示:本文所有操作均在 Windows Server 2016 Datacenter Edition(1607)下验证,Python 3.8.10(64位)通过官网 MSI 安装,安装路径为 C:\Python38\。请勿使用 Microsoft Store 版 Python,其权限模型与系统服务冲突。——精确到补丁号(1607),排除所有模糊地带。强调“MSI 安装”而非“zip 解压”,因为后者缺少注册表项,服务无法加载 Python DLL。
核心步骤(分四步,每步含原理+命令+验证):
- 准备 Python 脚本:给出最小可运行脚本
monitor.py,重点在if __name__ == '__main__':下添加win32serviceutil.InstallService调用(非必须,但 WizardWu 选择用此方式避免手动注册表操作); - 创建服务安装脚本:提供
install_service.py,核心是win32serviceutil.InstallService(MonitorService, 'PyMonitor', 'Python Monitor Service'),并解释MonitorService类必须继承win32serviceutil.ServiceFramework; - 执行安装与启动:
python install_service.py install→python install_service.py start,紧接着给出验证命令sc query PyMonitor,并说明返回STATE : 4 RUNNING才算成功; - 配置自动重启策略:
sc failure PyMonitor reset= 86400 actions= restart/60000/restart/60000//60000,详细解释reset=86400是“86400秒后重置失败计数器”,actions=后三个/60000分别对应“第一次失败后60秒重启、第二次失败后60秒重启、第三次失败后60秒重启”,最后的//60000是“此后所有失败都60秒重启”。
- 准备 Python 脚本:给出最小可运行脚本
陷阱排查(独立章节,占全文1/3):
- 陷阱1:服务启动后立即停止→ 原因:Python 脚本中
time.sleep(300)导致主线程阻塞,服务管理器认为启动超时;解决方案:改用win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)等待停止信号; - 陷阱2:事件查看器无日志→ 原因:
win32evtlogutil.ReportEvent需提前注册事件源,否则静默失败;解决方案:wevtutil im event.man导入事件清单; - 陷阱3:服务无法访问网络共享→ 原因:默认服务账户
LocalSystem无域凭据;解决方案:sc config PyMonitor obj= "DOMAIN\svc_account" password= "pwd"并赋予Log on as a service权限。
- 陷阱1:服务启动后立即停止→ 原因:Python 脚本中
这种结构,把一篇“教程”变成了“故障手册”。读者不是按顺序阅读,而是遇到问题时直奔对应陷阱标题,30秒内找到根因和解法。这才是真正服务于生产环境的内容设计。
3.2 代码风格与注释规范:每一行代码都在回答“为什么这么写”
WizardWu 的代码块绝非功能正确即可,其注释密度达到每3行代码就有1行解释性注释,且注释内容直指要害:
# 【关键】此处必须用 sys.exit(0) 而非 return,因为 Windows 服务主函数 # 返回非零值会被服务控制管理器视为启动失败,即使脚本逻辑已执行完毕 # 测试方法:在服务属性中勾选"允许服务与桌面交互",弹窗确认 sys.exit(0)# 【避坑】不要用 $(date +%s) 获取时间戳!OpenWrt 的 busybox date 不支持 %s # 会导致 backup_$(date +%s).tar.gz 文件名变成 backup_.tar.gz,覆盖前次备份 # 正确做法:用 $(cat /proc/uptime | awk '{print int($1)}') 获取系统启动秒数 backup_file="backup_$(cat /proc/uptime | awk '{print int($1)}').tar.gz"这种注释不解释语法(读者应懂$(...)是命令替换),只解释为什么在此场景下必须这样写,不这样写的灾难性后果是什么,以及如何验证你的写法是否正确。它把“知其然”压缩到最小,把“知其所以然”扩展到最大,迫使读者思考上下文约束,而非机械复制。
3.3 工具链深度绑定:如何让脚本在 OpenWrt 上“活下来”
OpenWrt 是 WizardWu 内容的重镇,其特殊性在于:资源极度受限(内存常<64MB)、软件包精简(无systemd、无cronie完整版)、内核定制(部分驱动缺失)。他的文章从不假设“安装 opkg update && opkg install python3”可行,而是深入到 BusyBox 的编译选项层面:
Python 运行时选择:明确指出 OpenWrt 21.02 官方固件的
python3-light包不含ssl模块,若脚本需 HTTPS 请求,必须用opkg install python3-full(体积增加 4MB,但值得);若空间紧张,则改用curl --cacert /etc/ssl/certs/ca-certificates.crt替代requests.get()。定时任务替代方案:当
crond不可用时,他提供procd服务脚本方案:# /etc/init.d/backup START=99 USE_PROCD=1 start_service() { # procd 启动一个后台进程,自动管理生命周期 procd_open_instance procd_set_param command /usr/bin/python3 /root/backup.py procd_set_param respawn ${respawn_timeout:-3600} ${respawn_retry:-5} ${respawn_threshold:-0} procd_close_instance }并解释
respawn参数:3600是“3600秒内最多重启5次”,5是“每次重启间隔5秒”,0是“无阈值限制”,这比crond的@reboot更可靠,因为procd是 OpenWrt 原生进程管理器。存储空间保护机制:针对 Flash 存储寿命,他强制脚本在写入前检查剩余空间:
# 【生死线】OpenWrt Flash 写入次数有限,必须防止日志撑爆 /tmp # 使用 df -k /tmp | awk 'NR==2 {print $4}' 获取剩余 KB 数,低于 10240(10MB)则删除最旧备份 free_kb=$(df -k /tmp | awk 'NR==2 {print $4}') if [ "$free_kb" -lt 10240 ]; then rm -f $(ls -t /tmp/backup_*.tar.gz | tail -n +2) # 保留最新一个,删其余 fi这种细节,只有真正在 OpenWrt 设备上烧过 Flash、修过变砖路由器的人,才会刻进骨子里。
4. 实操过程与核心环节实现:手把手复现一个完整项目
4.1 项目选定:为 OpenWrt 路由器编写“智能 DNS 切换脚本”
我们以 WizardWu 站点中一篇高热度文章《OpenWrt 下根据 WAN 口 IP 类型自动切换 DNS 服务器》为蓝本,完整复现其从需求分析到上线运行的全过程。该项目目标:当路由器 WAN 口获取到公网 IP(如 1.2.3.4)时,DNS 设为114.114.114.114;当获取到内网 IP(如 10.x.x.x、172.16.x.x、192.168.x.x)时,DNS 切为192.168.1.1(上游光猫 DNS),避免内网设备访问光猫管理页时被 DNS 劫持。
4.2 需求拆解与技术路线确认
第一步不是写代码,而是确认 OpenWrt 的网络模型:
- WAN 口 IP 由
ubus call network.interface.wan status获取,返回 JSON 中"ipv4-address": [{"address": "192.168.1.100", "mask": 24}]; - DNS 配置在
/etc/config/dhcp的config dnsmasq区块中,list server行指定 DNS 服务器; - OpenWrt 的网络事件由
hotplug.d触发,WAN 口状态变化时,/etc/hotplug.d/iface/下脚本会被调用。
因此技术路线明确:监听 WAN 接口事件 → 解析当前 IP → 匹配 IP 段规则 → 修改 dnsmasq 配置 → 重启 dnsmasq 服务。全程不依赖 Python,纯 Shell 实现,确保在任何 OpenWrt 固件上均可运行。
4.3 核心脚本编写与逐行注释
创建/etc/hotplug.d/iface/99-dns-switch:
#!/bin/sh # 【作用】WAN 接口状态变更时,自动切换 DNS 服务器 # 【触发】由 OpenWrt hotplug 机制调用,$INTERFACE=wlan, $ACTION=ifup/ifdown # 【注意】此脚本在 ifup 后执行,此时 WAN IP 已分配,但 dnsmasq 尚未重读配置 # 仅处理 WAN 接口的 ifup 事件 [ "$INTERFACE" = "wan" ] || exit 0 [ "$ACTION" = "ifup" ] || exit 0 # 【关键】获取 WAN 口 IPv4 地址,使用 ubus 而非 ifconfig,因 ifconfig 在 busybox 中输出不稳定 WAN_IP=$(ubus call network.interface.wan status 2>/dev/null | jsonfilter -e "$.ipv4-address[0].address" 2>/dev/null) # 【安全】若未获取到 IP,退出(如 DHCP 获取失败) [ -z "$WAN_IP" ] && exit 0 # 【IP 段判断逻辑】使用 case 语句,比正则更高效(busybox ash 不支持复杂正则) case "$WAN_IP" in 10.*|172.1[6-9].*|172.2[0-9].*|172.3[0-1].*|192.168.*) # 内网 IP,DNS 指向光猫 NEW_DNS="192.168.1.1" ;; *) # 公网 IP,DNS 指向 114 DNS NEW_DNS="114.114.114.114" ;; esac # 【配置修改】使用 uci 命令修改 /etc/config/dhcp,比直接编辑文件更安全(自动处理转义) # 先删除所有现有 server 行 uci delete dhcp.@dnsmasq[0].server # 添加新 DNS uci add_list dhcp.@dnsmasq[0].server="$NEW_DNS" # 【强制】提交更改,不加 -q 会输出冗余信息污染日志 uci commit dhcp # 【服务重启】使用 /etc/init.d/dnsmasq reload 而非 restart,减少服务中断时间 /etc/init.d/dnsmasq reload 2>/dev/null # 【日志】写入系统日志,便于后续排查 logger -t "dns-switch" "WAN IP $WAN_IP -> DNS set to $NEW_DNS" # 【验证】检查 dnsmasq 是否已加载新配置(可选,调试用) # echo "Current DNS: $(uci get dhcp.@dnsmasq[0].server 2>/dev/null)"提示:
jsonfilter是 OpenWrt 的轻量 JSON 解析工具,比jq小 10 倍,必须提前opkg install jsonfilter。若设备无此工具,可用awk '/address/ {gsub(/[^0-9.]/,"",$2); print $2}'替代,但健壮性下降。
4.4 部署与验证全流程
- 上传脚本:用
scp将脚本传至路由器/etc/hotplug.d/iface/99-dns-switch; - 设置权限:
chmod +x /etc/hotplug.d/iface/99-dns-switch; - 安装依赖:
opkg update && opkg install jsonfilter; - 手动触发测试:
- 断开 WAN 网线,执行
ifdown wan && ifup wan; - 观察日志:
logread | grep "dns-switch",应看到WAN IP 192.168.1.100 -> DNS set to 192.168.1.1; - 拔掉光猫网线,让 WAN 口获取公网 IP(或模拟),再执行
ifup wan,日志应显示切为114.114.114.114;
- 断开 WAN 网线,执行
- 持久化验证:重启路由器,
logread | grep "dns-switch"应有两条记录,证明 hotplug 在启动时也正确触发。
整个过程无需重启路由器,无需修改任何其他配置,5分钟内完成。这就是 WizardWu 式实操的魅力:没有“理论上可行”,只有“此刻就生效”。
4.5 性能与稳定性加固
生产环境不能只求“能用”,还需“稳用”。WizardWu 在同类文章中总会加入加固项:
防重复执行锁:在脚本开头添加:
LOCKFILE="/var/run/dns-switch.lock" if [ -f "$LOCKFILE" ] && kill -0 $(cat "$LOCKFILE") > /dev/null 2>&1; then exit 0 # 另一实例正在运行 fi echo $$ > "$LOCKFILE" trap "rm -f $LOCKFILE" EXIT避免 WAN 口快速抖动时,多个
ifup事件并发触发脚本,导致uci commit冲突。DNS 切换平滑性:
dnsmasq reload会清空 DNS 缓存,可能导致短暂解析失败。改为:# 先添加新 DNS 到 dnsmasq 配置,reload,再删除旧 DNS(避免配置为空) uci add_list dhcp.@dnsmasq[0].server="$NEW_DNS" uci commit dhcp /etc/init.d/dnsmasq reload # 删除旧 DNS(reload 后新配置已生效,旧 DNS 不再使用) uci delete dhcp.@dnsmasq[0].server uci commit dhcp确保任何时候
dnsmasq配置中至少有一个有效 DNS。失败降级机制:若
ubus调用失败,脚本不应静默退出,而是 fallback 到ifconfig:WAN_IP=$(ubus call network.interface.wan status 2>/dev/null | jsonfilter -e "$.ipv4-address[0].address" 2>/dev/null) if [ -z "$WAN_IP" ]; then # Fallback to ifconfig (less reliable but works) WAN_IP=$(ifconfig eth0.2 | awk '/inet addr/ {print $2}' | cut -d: -f2 2>/dev/null) fi
这些加固点,不是炫技,而是无数次线上事故后凝结的血泪经验。它们让脚本从“玩具”蜕变为“生产级工具”。
5. 常见问题与排查技巧实录:来自真实部署现场的 12 个高频故障
5.1 Hotplug 脚本不触发?先查这三件事
Hotplug 是 OpenWrt 的心脏,但也是新手最容易卡壳的地方。WizardWu 的排查清单,精准到命令级别:
| 检查项 | 验证命令 | 正常输出示例 | 异常含义 |
|---|---|---|---|
| hotplug 服务是否运行 | `ps | grep hotplug` | 245 root 1504 S /sbin/hotplug-call iface |
| 脚本权限是否正确 | ls -l /etc/hotplug.d/iface/99-dns-switch | -rwxr-xr-x 1 root root 1234 Jan 1 00:00 ... | 权限非755:chmod 755 |
| 脚本是否被 hotplug 识别 | `logread | grep "hotplug.*iface"` | hotplug(iface): called for wan ifup |
注意:
hotplug脚本名必须以两位数字开头(如99-xxx),数字越小越早执行。99-是安全选择,避开系统默认的00-到50-脚本。
5.2ubus call返回空?可能是接口名错了
OpenWrt 的接口名并非总是wan。在双 WAN 或 PPPoE 场景下,可能是wan_2、pppoe-wan。正确做法是:
# 列出所有网络接口 ubus list network.interface.* # 查看每个接口状态,找有 ipv4-address 的那个 for iface in $(ubus list network.interface.* | grep -v "network.interface."); do ip=$(ubus call $iface status 2>/dev/null | jsonfilter -e "$.ipv4-address[0].address" 2>/dev/null) [ -n "$ip" ] && echo "$iface -> $ip" doneWizardWu 在文章中强调:“永远不要假设接口名,用ubus list发现它”。
5.3uci commit后配置没生效?检查 uci batch 模式
uci commit有时不生效,是因为uci在 batch 模式下会缓存更改。解决方案:
- 临时修复:
uci commit dhcp && /etc/init.d/dnsmasq restart - 根本解决:在脚本中显式禁用 batch:
# 在 uci 命令前加 export UCI_BATCH=0 uci set dhcp.@dnsmasq[0].server="$NEW_DNS" uci commit dhcp
5.4 日志里全是jsonfilter: parse error?JSON 结构变了
OpenWrt 版本升级可能改变ubus call返回的 JSON 结构。例如 21.02 返回{"ipv4-address": [...]},而 22.03 可能返回{"ipv4-address": {"address": "..."}}。应对策略:
# 兼容两种结构的解析 WAN_IP=$(ubus call network.interface.wan status 2>/dev/null | \ (jsonfilter -e "$.ipv4-address[0].address" 2>/dev/null || \ jsonfilter -e "$.ipv4-address.address" 2>/dev/null))5.5 脚本执行后 DNS 没变?检查 dnsmasq 配置覆盖
/etc/config/dhcp中可能有多个config dnsmasq区块,uci默认操作第一个(@dnsmasq[0])。但某些固件会生成@dnsmasq[1]用于 DHCP 服务。验证命令:
# 列出所有 dnsmasq 配置 uci show dhcp | grep -A5 "dnsmasq" # 查看当前生效的 DNS(dnsmasq 进程实际读取的) ps | grep dnsmasq | grep -o "server=[^ ]*"若发现server=127.0.0.1#5353,说明dnsmasq正在使用dnsmasq.conf而非 uci 配置,需uci set dhcp.@dnsmasq[0].port='0'禁用内置 DNS,或uci set dhcp.@dnsmasq[0].noresolv='1'。
5.6 其他 7 个高频问题速查表
| 问题现象 | 根本原因 | 快速修复命令 | WizardWu 实操心得 |
|---|---|---|---|
脚本执行时报jsonfilter: not found | jsonfilter未安装或路径不在$PATH | opkg install jsonfilter && export PATH="/usr/bin:$PATH" | OpenWrt 的$PATH默认不含/usr/bin,export PATH必须在脚本开头执行 |
ifup wan后脚本执行,但logread无输出 | logger命令被重定向或 syslog 服务异常 | logger "test" && logread | tail -5 | 先用logger "test"验证 syslog 是否工作,再查脚本日志 |
DNS 切换后,nslookup google.com仍走旧 DNS | nslookup默认用/etc/resolv.conf,而 dnsmasq 作为本地 DNS 代理 | nslookup google.com 127.0.0.1 | 测试必须指定127.0.0.1,/etc/resolv.conf的nameserver是给其他程序用的 |
脚本中case语句匹配失败 | WAN_IP变量含不可见字符(如换行符) | WAN_IP=$(echo "$WAN_IP" | tr -d '\r\n') | ubus输出末尾常带\n,case匹配会失败,必须tr -d清理 |
uci commit后uci show dhcp显示正确,但dnsmasq未加载 | dnsmasq配置缓存未刷新 | /etc/init.d/dnsmasq reload→/etc/init.d/dnsmasq restart | reload有时不重读 uci 配置,restart是终极方案 |
脚本在ifdown时也被触发,导致 DNS 错乱 | hotplug的$ACTION在ifdown时也为ifup(bug) | [ "$ACTION" = "ifup" ] && [ -n "$WAN_IP" ]双重校验 | ifdown时WAN_IP为空,加此判断可过滤 |
| 路由器重启后,脚本失效 | /etc/hotplug.d/iface/目录在 overlayfs 中未持久化 | mkdir -p /overlay/etc/hotplug.d/iface/ && cp /etc/hotplug.d/iface/99-dns-switch /overlay/etc/hotplug.d/iface/ | OpenWrt 的/etc是 overlayfs,修改需写入/overlay/etc/才持久 |
这些速查表,是 WizardWu 在论坛、邮件列表中回复上千个用户提问后提炼的精华。它不讲原理
