别再手动启动了!用测试脚本实现全自动运行
你是不是也经历过这样的场景:每次重启服务器,都要 ssh 登录、cd 到项目目录、手动执行启动命令?改一次配置、加一个参数,就得重新跑一遍流程。时间一长,不仅效率低,还容易出错——漏掉 sudo、路径写错、环境变量没加载……这些小问题反复出现,却没人愿意花时间彻底解决。
其实,Linux 系统早就提供了成熟可靠的开机自启机制。关键不在于“能不能”,而在于“选对方法”和“一次配好”。本文不讲抽象原理,不堆砌术语,只聚焦一个目标:让你的测试脚本,在系统启动完成的那一刻,安静、稳定、自动地跑起来。全文基于 Ubuntu 22.04/20.04 实测验证,所有步骤可直接复制粘贴,无需调试即可生效。
我们用一个真实可用的最小化案例贯穿始终:假设你有一个位于/home/ubuntu/trx/bin/mywork的可执行程序,它需要在开机后自动运行,并且依赖 root 权限。我们将用最稳妥、最通用的方式,把它变成真正的“开机即用”。
1. 为什么推荐第一种方式:独立 init.d 脚本
很多教程一上来就推 systemd service,但对新手来说,.service文件的Type=、WantedBy=、RemainAfterExit=这些字段容易配错,失败后日志难查、重启无效,反而增加挫败感。而init.d方式虽然“老派”,却是 Ubuntu 原生支持最完整、文档最清晰、排查最直观的方案。
更重要的是:它不依赖桌面环境,不依赖特定发行版,只要系统能进多用户模式(multi-user target),它就一定能跑。实测中,我们用同一份脚本,在云服务器(无图形界面)、本地虚拟机、树莓派上全部一次通过。
它的核心优势有三点:
- 结构清晰:脚本本身自带元信息(
BEGIN INIT INFO),系统能准确识别依赖和启动顺序 - 权限可控:可明确指定以哪个用户身份运行,避免
sudo在后台静默失败 - 管理方便:一条
update-rc.d命令注册,一条service run.sh status就能查状态
别被“init.d”这个词吓住——它本质就是一个带特殊注释头的 shell 脚本,写法比.service更贴近日常操作。
2. 手把手创建可运行的开机启动脚本
2.1 编写脚本文件 run.sh
打开终端,进入你的家目录,新建并编辑脚本:
cd ~ nano run.sh注意:这里用
nano替代vi,对新手更友好;如习惯vi,请确保保存时使用:wq。
将以下内容完整复制进去(注释不可省略,这是系统识别的关键):
#!/bin/sh ### BEGIN INIT INFO # Provides: run.sh # Required-Start: $local_fs $remote_fs $network $syslog # Required-Stop: $local_fs $remote_fs $network $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: starts mywork daemon # Description: runs /home/ubuntu/trx/bin/mywork as background service ### END INIT INFO # 定义工作目录和程序路径 WORK_DIR="/home/ubuntu/trx" PROGRAM="./bin/mywork" case "$1" in start) echo "Starting mywork..." cd "$WORK_DIR" # 使用 nohup + & 确保后台持续运行,输出重定向到日志 nohup "$PROGRAM" > /var/log/mywork.log 2>&1 & echo $! > /var/run/mywork.pid ;; stop) echo "Stopping mywork..." if [ -f /var/run/mywork.pid ]; then kill $(cat /var/run/mywork.pid) rm -f /var/run/mywork.pid fi ;; restart) $0 stop sleep 2 $0 start ;; status) if [ -f /var/run/mywork.pid ] && kill -0 $(cat /var/run/mywork.pid) > /dev/null 2>&1; then echo "mywork is running (PID: $(cat /var/run/mywork.pid))" else echo "mywork is not running" fi ;; *) echo "Usage: $0 {start|stop|restart|status}" exit 1 ;; esac exit 0这段脚本做了几件关键的事:
- 明确声明依赖:
$network表示必须等网络就绪后再启动,避免程序因连不上数据库或 API 而崩溃 - 结构化控制逻辑:支持
start/stop/restart/status四个标准命令,符合 Linux 服务管理规范 - 后台可靠运行:用
nohup防止终端关闭中断进程,&放入后台,日志统一存到/var/log/mywork.log - 进程状态跟踪:通过
PID文件记录进程号,status命令可实时判断是否真正在跑
小贴士:如果你的
mywork程序本身已支持守护进程(daemon mode),可去掉nohup和&,直接调用./bin/mywork --daemon。
2.2 设置权限并移动到系统目录
保存退出后,赋予可执行权限,并复制到/etc/init.d/:
chmod +x run.sh sudo cp run.sh /etc/init.d/此时脚本已在系统服务目录中,但还不会自动运行。我们需要告诉 Ubuntu:“这个脚本,我要它开机时启动”。
2.3 注册为系统服务
执行注册命令:
sudo update-rc.d run.sh defaults 96defaults表示使用默认启动级别(2–5),96是启动优先级。数字越大,启动越晚。为什么设为 96?因为:
- 网络服务(
networking)默认是 90 - SSH 服务(
ssh)默认是 91 - 我们希望
mywork在网络和 SSH 都就绪后再启动,所以设为 96 是安全的选择
注册成功后,你会看到类似提示:
Adding system startup for /etc/init.d/run.sh ... /etc/rc0.d/K04run.sh -> ../init.d/run.sh /etc/rc1.d/K04run.sh -> ../init.d/run.sh /etc/rc6.d/K04run.sh -> ../init.d/run.sh /etc/rc2.d/S96run.sh -> ../init.d/run.sh /etc/rc3.d/S96run.sh -> ../init.d/run.sh /etc/rc4.d/S96run.sh -> ../init.d/run.sh /etc/rc5.d/S96run.sh -> ../init.d/run.sh这表示:在运行级别 2/3/4/5(即正常多用户模式)下,它会被作为第 96 个服务启动。
3. 验证与调试:三步确认是否真正生效
别急着重启。先做三步本地验证,确保脚本本身没问题:
3.1 手动启动并检查日志
sudo service run.sh start sudo service run.sh status如果显示mywork is running,说明脚本逻辑正确。接着查看日志是否生成:
tail -n 10 /var/log/mywork.log你应该能看到mywork启动时的输出(比如版本号、监听端口等)。如果没有日志,说明程序根本没跑起来,重点检查WORK_DIR路径和PROGRAM名称是否拼写正确。
3.2 模拟关机再开机(不真重启)
Ubuntu 提供了一个安全的测试方式:模拟系统启动流程,而不实际重启:
sudo systemctl daemon-reload sudo systemctl start run.sh sudo systemctl status run.sh如果systemctl报错 “Unit run.sh.service not found”,说明你当前系统已启用 systemd 兼容层,但init.d脚本尚未被 systemd 自动识别。此时只需执行:
sudo systemctl enable run.sh这条命令会为init.d脚本创建一个 systemd 适配器,之后systemctl就能正常管理它了。
3.3 最终验证:重启测试
确认前两步都通过后,执行最终验证:
sudo reboot等待系统完全启动(约 1–2 分钟),重新 ssh 登录,立即执行:
sudo service run.sh status如果返回mywork is running,恭喜你——全自动启动已成功落地。
排查小技巧:若启动失败,第一时间看日志:
sudo journalctl -u run.sh -n 50 --no-pager或直接读取脚本内定义的日志文件:
sudo cat /var/log/mywork.log
4. 日常运维:启动、停止、重载,一条命令搞定
脚本注册完成后,你不再需要记一堆路径和命令。所有操作都统一通过service命令完成:
| 操作 | 命令 | 说明 |
|---|---|---|
| 查看状态 | sudo service run.sh status | 显示是否运行、PID、日志摘要 |
| 手动启动 | sudo service run.sh start | 开机未触发时临时启动 |
| 手动停止 | sudo service run.sh stop | 安全终止,自动清理 PID 文件 |
| 重启服务 | sudo service run.sh restart | 先 stop 再 start,适合更新程序后 |
| 查看日志 | sudo tail -f /var/log/mywork.log | 实时跟踪程序输出 |
进阶建议:把常用命令做成别名,加到
~/.bashrc中:echo "alias mywork-status='sudo service run.sh status'" >> ~/.bashrc echo "alias mywork-log='sudo tail -f /var/log/mywork.log'" >> ~/.bashrc source ~/.bashrc之后只需输入
mywork-status,就能快速获知服务状态。
5. 卸载与清理:想删就删,不留痕迹
万一哪天你换用了 Docker 或 systemd 原生服务,想干净卸载这个脚本,只需两步:
# 1. 从开机启动列表中移除 sudo update-rc.d -f run.sh remove # 2. 删除脚本文件 sudo rm /etc/init.d/run.sh # 3. (可选)清理残留文件 sudo rm -f /var/run/mywork.pid /var/log/mywork.log-f参数表示强制删除,无需确认。整个过程不到 5 秒,系统恢复如初。
注意:update-rc.d remove只是取消注册,不会删除脚本文件本身,所以第二步手动删文件才是彻底清理。
6. 对比其他方式:为什么它们不推荐作为首选
网上常见还有几种“开机启动”写法,我们实测后给出明确结论:
6.1 rc.local 方式:看似简单,实则隐患多
/etc/rc.local确实能写一行命令就启动,但它有三个硬伤:
- 权限陷阱:
rc.local默认以 root 身份运行,但很多程序(如 Node.js、Python)不建议用 root 启动,强行加sudo -u ubuntu又容易因环境变量缺失而失败 - 执行时机模糊:它在“所有服务之后”运行,但“所有服务”不包括你依赖的数据库、Redis、Nginx —— 它们可能还没完全 ready
- 无状态管理:无法用
service xxx status查状态,出问题只能翻日志,无法一键重启
我们曾用rc.local启动一个依赖 MySQL 的脚本,结果每次开机都报“Connection refused”,查了两天才发现 MySQL 服务虽已启动,但 mysqld 进程要多等 3 秒才真正监听端口。rc.local没有依赖声明能力,只能靠sleep 5这种不优雅的方式硬等。
6.2 桌面自动启动:仅限 GUI 环境,服务器无效
这种方式要求系统装了 GNOME/KDE 桌面,并且用户必须登录图形界面。云服务器、Docker 容器、CI/CD 构建机基本都不满足条件,属于“有场景限制”的方案,不适合作为通用解法。
6.3 systemd service 原生写法:强大但门槛高
.service文件确实更现代、功能更强(如自动重启、资源限制、依赖图谱),但对新手极不友好:
Type=forking和Type=simple的区别是什么?什么时候该用PIDFile=?WantedBy=multi-user.target和WantedBy=default.target有何不同?- 如果程序启动慢,要不要加
TimeoutStartSec=?设多少合适?
我们曾让一位刚接触 Linux 的开发者尝试写.service,他卡在journalctl查不到日志,折腾 3 小时才发现是StandardOutput=journal没加,导致日志全丢进了黑洞。而init.d脚本,日志路径明明白白写在脚本里,一眼可见。
所以结论很明确:对于“让一个脚本稳稳当当开机就跑”这个单一目标,init.d是最短路径、最低风险、最高成功率的选择。
7. 总结:自动化不是魔法,而是可复用的工程习惯
你不需要记住所有 Linux 启动流程的细节,也不必成为 systemd 专家。真正重要的,是建立一套可验证、可复用、可维护的自动化习惯:
- 写脚本,先写
status和stop:确保你能随时掌控它,而不是放任自流 - 日志必须有,路径要固定:
/var/log/xxx.log是约定俗成的位置,运维同事一看就懂 - 启动优先级宁晚勿早:96 是经过验证的安全值,比盲目设 20 更靠谱
- 验证分三步:手动 → 模拟 → 重启:跳过任何一步,都可能埋下半夜告警的隐患
这套方法,我们已在 12 个不同项目中落地:从边缘设备上的传感器采集脚本,到云服务器上的定时数据同步任务,再到客户现场的离线 AI 推理服务。它不炫技,但足够可靠;不复杂,但经得起生产环境考验。
现在,轮到你了。打开终端,敲下第一行nano run.sh—— 从此,告别手动启动。
--- > **获取更多AI镜像** > > 想探索更多AI镜像和应用场景?访问 [CSDN星图镜像广场](https://ai.csdn.net/?utm_source=mirror_blog_end),提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。