测试开机启动脚本避坑指南,这些细节不能忽略
你是不是也遇到过这样的情况:辛辛苦苦写好一个开机自启脚本,放进/etc/rc.local,重启后却发现命令根本没执行?服务没起来、网络没配置、设备没初始化……一切照旧。不是脚本写错了,也不是权限没给足,问题就出在那些看似微不足道、文档里轻描淡写、但系统却极其较真的“小细节”上。
这篇指南不讲大道理,不堆概念,只聚焦真实测试环境(Ubuntu 16.04 和 Tina 系统)中踩过的坑、验证过的解法、必须检查的环节。它不是“如何写一个 rc.local 脚本”的入门课,而是你准备重启前,应该再看一眼的 checklist。
1./etc/rc.local不是万能胶,先确认它还在“岗”
很多人默认rc.local一定存在、一定生效——这是第一个误区。现代 Linux 发行版(尤其是 systemd 管理的系统)早已弱化甚至默认禁用它。Ubuntu 16.04 虽仍支持,但需手动启用;Tina 系统则因定制程度高,更需实测确认。
1.1 检查文件是否存在且可执行
ls -l /etc/rc.local理想输出应为:
-rwxr-xr-x 1 root root ... /etc/rc.local注意两点:
- 存在性:若提示
No such file or directory,需手动创建; - 可执行位:权限中必须有
x(执行权限),否则 systemd 会直接跳过。若无x,运行以下命令修复:
sudo chmod +x /etc/rc.local1.2 验证 systemd 服务是否启用
Ubuntu 16.04 使用 systemd,rc.local实际由rc-local.service控制。即使文件存在,服务未启用,脚本照样静默失效。
检查服务状态:
systemctl status rc-local若显示inactive (dead)或disabled,说明服务未启用。启用并启动它:
sudo systemctl enable rc-local sudo systemctl start rc-local关键提示:
enable是开机自启,start是立即运行一次。两者缺一不可。执行后再次status,应看到active (exited)。
1.3 Tina 系统的特殊性:别信默认配置
Tina 是基于 OpenWrt 的嵌入式系统,其 init 流程与标准 Linux 差异较大。/etc/rc.local可能被精简、重命名,或根本未集成到启动链中。
验证方法最直接:在rc.local开头插入一行日志:
echo "$(date): rc.local started" >> /tmp/rclocal.log重启后检查/tmp/rclocal.log是否生成。若无记录,说明该文件未被调用,需查阅 Tina 文档或改用其原生启动机制(如/etc/init.d/下的自定义服务脚本)。
2. 脚本语法:exit 0是句号,不是可选项
参考文档里那句“exit 0不能少”,绝非虚言。它不是“建议”,而是rc.local的硬性契约。
2.1 为什么必须exit 0?
/etc/rc.local在 systemd 中被当作一个 shell 脚本执行。systemd 要求该脚本以成功退出码(0)结束,才认为启动阶段完成。若脚本末尾无exit,或最后一条命令失败(返回非0码),systemd 会卡在rc-local.service,导致后续服务(如网络、SSH)无法正常启动,整机可能卡在黑屏或无法 SSH 登录。
2.2 常见错误写法与修正
❌ 错误:脚本末尾只有命令,无exit
#!/bin/sh -e # rc.local ifconfig wlan0 up ifconfig wlan0 192.168.1.100 # 缺少 exit 0!正确:明确以exit 0结束
#!/bin/sh -e # rc.local ifconfig wlan0 up ifconfig wlan0 192.168.1.100 exit 0更健壮:捕获命令失败,避免因单条命令失败导致整个脚本中断
#!/bin/sh -e # rc.local ifconfig wlan0 up || echo "Warning: wlan0 up failed" ifconfig wlan0 192.168.1.100 || echo "Warning: wlan0 IP config failed" exit 0注意:
-e参数表示“任一命令失败即退出脚本”。若你希望部分命令失败不影响整体,需显式用|| true或移除-e,但务必保证最终exit 0。
3. 执行环境陷阱:你以为的“当前环境”,系统并不认
在终端里能跑通的命令,放进rc.local就失败?大概率是环境变量或路径惹的祸。
3.1 PATH 环境变量缺失
rc.local由 root 用户在极简环境中执行,其PATH通常只有/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin。如果你的命令在/opt/myapp/bin/下,或依赖node、python3等非基础路径的二进制文件,会直接报command not found。
解决方案:永远使用绝对路径
# ❌ 危险:依赖 PATH python3 /root/myscript.py # 安全:指定完整路径 /usr/bin/python3 /root/myscript.py获取绝对路径的方法:
which python3 # 输出 /usr/bin/python3 readlink -f $(which python3) # 更可靠,处理软链接3.2 当前工作目录不确定
rc.local启动时,当前目录(pwd)可能是/、/root或其他任意路径。若你的脚本依赖相对路径读取配置文件或写入日志,极易出错。
解决方案:脚本内主动切换目录
#!/bin/sh -e cd /root/myproject || exit 1 ./start_service.sh exit 0或在命令中直接使用绝对路径:
# 读取配置 cat /root/myproject/config.json # 写入日志 echo "Started at $(date)" >> /root/myproject/logs/startup.log3.3 服务依赖未就绪:网络、磁盘、USB 设备
rc.local在系统启动早期执行,此时网络接口可能未完全初始化(wlan0存在但未UP),外部 USB 设备可能未被识别,挂载点(如/mnt/data)尚未挂载。
解决方案:添加等待与检查逻辑
- 等网络就绪(针对
ifconfig类命令):
# 等待 wlan0 接口出现并 UP for i in $(seq 1 30); do if ip link show wlan0 | grep -q "state UP"; then break fi sleep 1 done ifconfig wlan0 192.168.1.100- 等挂载点就绪:
# 等待 /mnt/data 挂载完成 for i in $(seq 1 10); do if mount | grep -q "/mnt/data"; then break fi sleep 1 done cp /tmp/data.txt /mnt/data/- 等 USB 设备识别(Tina 常见):
# 等待 /dev/ttyUSB0 出现 for i in $(seq 1 15); do if [ -c "/dev/ttyUSB0" ]; then stty -F /dev/ttyUSB0 115200 break fi sleep 1 done4. 权限与用户:别让 root 也“没权限”
rc.local默认以 root 身份运行,但这不意味着万事大吉。某些操作仍需额外授权。
4.1 systemd 服务冲突:不要重复管理同一服务
若你试图在rc.local中启动一个已有 systemd 服务(如nginx、mosquitto),会导致端口占用、进程冲突。rc.local启动的进程是“孤儿”,不受 systemd 监控,无法systemctl restart。
正确做法:禁用原生服务,交由 rc.local 管理
sudo systemctl disable nginx # 停止 systemd 管理 # 然后在 rc.local 中写: /usr/sbin/nginx -c /etc/nginx/nginx.conf或反之:彻底放弃 rc.local,改用标准 systemd 服务(推荐长期方案)。
4.2 文件系统只读:Tina 系统常见问题
Tina 的根文件系统常设为只读(ro),/etc/rc.local若被修改过,重启后可能恢复默认,或写入失败。
验证与修复:
# 查看根分区挂载状态 mount | grep " / " # 若显示 `ro`,需在启动参数中改为 `rw`,或在 Tina 配置中调整 # 临时修复(仅本次有效): sudo mount -o remount,rw /5. 调试与验证:重启不是唯一答案,日志才是真相
盲目重启十次,不如读懂一行日志。
5.1 查看 rc-local.service 日志
所有rc.local执行过程均被 systemd 记录:
sudo journalctl -u rc-local -n 50 --no-pager重点关注:
ExecStart=行:显示实际执行的命令;stderr或error关键字:直接指出哪行命令失败;exited, code=exited, status=1/FAILURE:明确失败退出码。
5.2 在脚本中添加详细日志
在rc.local关键步骤前后加入日志,让问题无处遁形:
#!/bin/sh -e echo "$(date): [START] rc.local execution" >> /var/log/rclocal.log echo "$(date): Checking wlan0..." >> /var/log/rclocal.log if ip link show wlan0 >/dev/null 2>&1; then echo "$(date): wlan0 exists, bringing up..." >> /var/log/rclocal.log ifconfig wlan0 up echo "$(date): wlan0 UP success" >> /var/log/rclocal.log else echo "$(date): ERROR: wlan0 not found!" >> /var/log/rclocal.log fi echo "$(date): [END] rc.local finished" >> /var/log/rclocal.log exit 05.3 模拟启动环境测试
避免每次重启验证,用以下命令模拟rc.local的执行环境:
sudo /bin/sh -e /etc/rc.local此命令以 root 身份、相同 shell 选项(-e)运行脚本,能快速暴露路径、权限、环境变量问题。
6. 替代方案思考:rc.local 是捷径,但未必是终点
rc.local简单直接,适合快速验证。但对生产环境或复杂需求,它暴露了明显短板:无依赖管理、无重启策略、无健康检查。
6.1 Ubuntu 16.04 推荐:迁移到 systemd 服务
为你的脚本创建专属 service 文件(如/etc/systemd/system/myscript.service):
[Unit] Description=My Custom Startup Script After=network.target [Service] Type=oneshot ExecStart=/usr/local/bin/myscript.sh RemainAfterExit=yes User=root [Install] WantedBy=multi-user.target启用:
sudo systemctl daemon-reload sudo systemctl enable myscript.service sudo systemctl start myscript.service优势:自动处理依赖(After=network.target)、失败自动告警、支持restart、日志统一管理。
6.2 Tina 系统推荐:遵循 OpenWrt 启动框架
Tina 基于 OpenWrt,应优先使用/etc/init.d/机制:
# 创建服务脚本 /etc/init.d/myscript #!/bin/sh /etc/rc.common START=99 USE_PROCD=1 start_service() { procd_open_instance procd_set_param command /usr/bin/python3 /root/myscript.py procd_set_param respawn procd_close_instance }然后:
chmod +x /etc/init.d/myscript /etc/init.d/myscript enable /etc/init.d/myscript start这能获得 OpenWrt 的完整生命周期管理(启动、停止、重启、状态查询)。
总结
测试开机启动脚本,本质是与系统启动流程的一场精密对话。那些被忽略的“小细节”,恰恰是对话中最重要的语法和语境。本文梳理的六个核心避坑点——从确认rc.local机制可用,到exit 0的强制约定;从环境变量与路径的绝对化,到服务依赖的主动等待;从权限与只读文件系统的应对,再到日志驱动的精准调试——每一条都源于真实场景的反复验证。
记住,一个可靠的开机脚本,不在于它写了多少行代码,而在于它能否在每一次冷启动中,安静、稳定、准确地完成使命。下次当你编辑完rc.local,请务必对照这份清单,逐项核验。重启前的这五分钟,往往比重启后的半小时排查更高效。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。