手把手教你创建开机启动项,只需一个测试脚本
你有没有遇到过这样的情况:写好了一个监控脚本、日志清理工具,或者一个简单的服务程序,每次重启服务器后都要手动运行一次?既麻烦又容易遗漏。其实,Linux系统早就为你准备好了标准机制——开机自启动脚本。它不依赖任何第三方工具,原生支持,稳定可靠,而且只需要几条命令就能搞定。
本文不是讲systemd的复杂单元文件,也不是堆砌一堆配置模板。我们聚焦最轻量、最通用、最易验证的方式:基于传统SysV init风格的开机启动项。无论你用的是CentOS 7(兼容模式)、Ubuntu 18.04或更早版本,甚至某些嵌入式Linux发行版,这套方法都完全适用。我们将从零开始,用一个真实可运行的测试脚本,带你走完“编写→注册→验证”的完整闭环。整个过程不需要安装额外软件,不修改核心配置,所有操作都在终端里敲几行命令,5分钟内就能看到效果。
最重要的是,这个方法足够“傻瓜”——即使你刚接触Linux一个月,只要能复制粘贴、看懂提示信息,就能成功。文中的每一步都配有清晰说明和典型输出示例,避免你卡在某个报错上反复查资料。现在,让我们开始。
1. 编写并部署测试脚本
我们要做的第一件事,是准备一个真正能被系统识别和执行的脚本。它不能只是简单打印一行字,而要具备标准启动脚本的基本结构:支持start、stop、status等子命令,并能正确返回退出码。这样系统在启动、关机或手动管理时才能准确判断它的状态。
下面是一个精简但完整的测试脚本,功能明确:启动时在/tmp下创建一个带时间戳的标记文件,停止时删除它,查询状态则检查该文件是否存在。它足够简单,便于你快速理解逻辑;又足够规范,能通过系统启动框架的校验。
#!/bin/bash # /etc/init.d/mytest.sh # chkconfig: 2345 99 01 # description: A simple test service for boot startup ### BEGIN INIT INFO # Provides: mytest # Required-Start: $local_fs $network # Required-Stop: $local_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Test startup script # Description: This script demonstrates how to add a service to system boot. ### END INIT INFO SCRIPT_NAME="mytest" MARKER_FILE="/tmp/mytest_boot_marker_$(date +%s)" case "$1" in start) echo "Starting $SCRIPT_NAME..." touch "$MARKER_FILE" 2>/dev/null if [ $? -eq 0 ]; then echo "$SCRIPT_NAME started successfully. Marker created: $MARKER_FILE" else echo "Failed to start $SCRIPT_NAME." exit 1 fi ;; stop) echo "Stopping $SCRIPT_NAME..." rm -f "$MARKER_FILE" echo "$SCRIPT_NAME stopped." ;; status) if [ -f "$MARKER_FILE" ]; then echo "$SCRIPT_NAME is running. Marker file exists." ls -l "$MARKER_FILE" else echo "$SCRIPT_NAME is not running. Marker file missing." fi ;; restart) $0 stop sleep 1 $0 start ;; *) echo "Usage: $0 {start|stop|status|restart}" exit 2 ;; esac exit 0这段脚本的关键点在于:
- Shebang行:
#!/bin/bash确保使用bash解释器执行,避免因默认shell不同导致语法错误。 - 注释块:
chkconfig行和### BEGIN/END INIT INFO块是给系统启动管理器看的“说明书”。它告诉系统这个脚本应该在哪些运行级别(2345)启动,启动优先级(99),停止优先级(01),以及它的依赖关系。虽然现代系统可能不严格读取这些,但保留它们是最佳实践,能提升兼容性。 - case结构:清晰分隔
start、stop、status等动作,每个分支都有明确的输出和错误处理。 - 标记文件:使用
/tmp目录(系统重启后自动清空)和时间戳,确保每次启动都生成唯一文件,方便你直观验证是否真的执行了。
现在,把上面的代码保存为/etc/init.d/mytest.sh。你可以用nano或vim编辑:
sudo nano /etc/init.d/mytest.sh粘贴内容后,按Ctrl+O保存,Ctrl+X退出。接着,必须赋予它可执行权限,否则系统无法运行:
sudo chmod +x /etc/init.d/mytest.sh最后,手动测试一下脚本是否工作正常:
# 启动脚本 sudo /etc/init.d/mytest.sh start # 检查状态 sudo /etc/init.d/mytest.sh status # 停止脚本 sudo /etc/init.d/mytest.sh stop你应该能看到类似“Marker created”、“is running”等提示。如果一切顺利,说明脚本本身没有问题,可以进入下一步。
2. 确认系统运行级别与启动目录
在Linux中,“开机启动”并不是一个笼统的概念,而是精确绑定到系统的“运行级别”(Runlevel)。不同的运行级别代表系统不同的工作状态,比如单用户模式、多用户文本模式、图形界面模式等。系统启动时,会根据默认的运行级别,去加载对应目录下的启动脚本。
因此,我们必须先搞清楚你的系统默认运行级别是什么,然后才能知道该把启动链接放在哪个目录里。
最直接的方法是使用runlevel命令:
runlevel这条命令会输出两个数字,例如N 5。第一个数字(N)表示上次的运行级别(N代表“None”,即系统刚启动),第二个数字(5)才是当前的运行级别。对于绝大多数桌面版Ubuntu和带GUI的CentOS,这个数字通常是5;而对于纯服务器环境(无图形界面),则很可能是3或2。
小知识:运行级别
0是关机,1是单用户维护模式,2-5是多用户模式,6是重启。其中2、3、4、5通常都配置为多用户模式,区别在于是否启用网络、NFS等服务。所以,我们的脚本一般会同时注册到2345这几个级别。
确认了运行级别,接下来就是找到对应的启动目录。系统约定俗成,所有运行级别x的启动脚本链接,都存放在/etc/rcx.d/目录下。例如:
- 运行级别
3→/etc/rc3.d/ - 运行级别
5→/etc/rc5.d/
这些目录里的文件,其实都是指向/etc/init.d/中真实脚本的软链接。它们的名字有严格格式:以S开头表示“Start”(启动),以K开头表示“Kill”(停止),后面紧跟两位数字(01-99)表示执行顺序,最后是服务名。
例如,S20network表示在网络服务启动序列中排第20位,K80apache2表示在关机序列中排第80位停止Apache。
所以,如果你的runlevel输出是N 5,那么你就需要进入/etc/rc5.d/目录。执行:
cd /etc/rc5.d/ ls -l你会看到一长串以S和K开头的链接。现在,你的任务就是在这里添加一个新的链接,指向我们刚刚创建的/etc/init.d/mytest.sh。
3. 创建启动软链接
进入正确的rcx.d目录后,就可以用ln -s命令创建软链接了。命令格式非常简单:
sudo ln -s /etc/init.d/mytest.sh S99mytest这里有几个关键细节需要你注意:
sudo:因为/etc/rc5.d/是系统目录,普通用户没有写入权限,必须加sudo。-s:表示创建“符号链接”(软链接),而不是硬链接。这是必须的,因为软链接可以跨文件系统,且能正确指向原始脚本。/etc/init.d/mytest.sh:这是你脚本的绝对路径,必须写全,不能省略。S99mytest:这是链接的名字。S表示启动,99是启动序号,mytest是服务名。序号99意味着它会在绝大多数其他服务之后启动。这很安全,因为你的测试脚本不依赖数据库或网络服务,晚一点启动完全没问题。如果你的服务有强依赖(比如必须等MySQL先启动),就把序号调小,比如S20mytest。
执行完这条命令后,用ls -l再查看一遍目录内容:
ls -l S99mytest你应该能看到类似这样的输出:
lrwxrwxrwx 1 root root 22 Jun 10 14:30 S99mytest -> /etc/init.d/mytest.sh这行输出清晰地告诉你:S99mytest是一个符号链接(l开头),它指向了/etc/init.d/mytest.sh。至此,注册工作就完成了。
为什么不用update-rc.d或chkconfig?
Ubuntu系统常用update-rc.d,CentOS常用chkconfig,它们是更高层的封装工具,能自动帮你处理所有运行级别的链接。但本文选择手动ln -s,是因为它最透明、最可控、最“底层”。你一眼就能看清链接在哪、指向哪、名字是什么,没有任何黑盒。当出现问题时,排查起来也最直接——删掉那个链接就行,无需记忆复杂的工具命令。
4. 验证启动项是否生效
现在,脚本已就位,链接已创建,万事俱备,只欠东风。但别急着重启!在重启之前,我们可以用一个更安全、更快捷的方式来模拟验证:手动触发系统启动流程。
Linux系统在启动时,会依次执行/etc/rcx.d/目录下所有以S开头的脚本。我们可以模仿这个过程,手动运行一遍:
# 切换到rc5.d目录(根据你的运行级别调整) cd /etc/rc5.d/ # 手动执行所有S开头的脚本(包括我们刚加的) sudo ./S99mytest start如果输出显示“started successfully”,并且/tmp下确实生成了标记文件,那就说明链接和脚本都工作正常。
但这还不够。真正的考验是系统启动时的自动执行。为了不中断你的工作,我们不建议立刻reboot。一个更优雅的验证方式是:切换运行级别。
Linux允许你在不重启的情况下,临时切换到另一个运行级别。例如,从图形界面(级别5)切换到多用户文本模式(级别3),系统会先执行所有K开头的停止脚本,再执行S开头的启动脚本。这是一个完美的“微型重启”测试。
执行以下命令:
# 切换到运行级别3(多用户文本模式) sudo telinit 3 # 等待几秒,然后切回5(图形界面) sudo telinit 5在切换过程中,系统会自动执行/etc/rc3.d/和/etc/rc5.d/下的脚本。如果你的脚本被正确注册到了rc5.d,那么在telinit 5时,它就会被再次启动。
切换回来后,立即检查:
sudo /etc/init.d/mytest.sh status如果状态显示“is running”,恭喜你,你的开机启动项已经成功注册!它会在每一次系统启动时,自动运行。
5. 故障排查与常见问题
即使严格按照步骤操作,有时也会遇到意外。别担心,下面列出几个最常遇到的问题及其解决方法,帮你快速定位和修复。
问题1:/etc/rc5.d/目录不存在
现象:cd /etc/rc5.d/报错No such file or directory。
原因:你的系统可能使用的是systemd,并且默认没有创建传统的rcx.d目录(尤其是较新的Ubuntu 20.04+或CentOS 8+)。
解决方案:首先确认你的系统是否真的用systemd。运行ps -p 1,如果输出中包含systemd,那你就处于systemd时代。此时,rcx.d目录可能被废弃,但好消息是,systemd提供了向后兼容的sysv-generator,它会自动将/etc/init.d/下的脚本转换为systemd服务。你只需要确保脚本有正确的### BEGIN INIT INFO块(我们已经在第一步写好了),然后执行:
sudo systemctl daemon-reload sudo systemctl enable mytestenable命令会自动为你创建一个mytest.service的软链接,效果等同于rcx.d链接。
问题2:脚本启动失败,status显示“not running”
现象:手动运行start没问题,但telinit 5后status却显示未运行。
原因:最常见的是脚本权限问题。/etc/init.d/下的脚本必须对root可执行,但有时chmod没生效,或者脚本被保存成了Windows格式(CRLF换行符)。
解决方案:
- 再次检查权限:
ls -l /etc/init.d/mytest.sh,确保有x标志。 - 检查换行符:
file /etc/init.d/mytest.sh。如果输出包含CRLF,说明是Windows格式。用dos2unix命令修复:sudo dos2unix /etc/init.d/mytest.sh。
问题3:ln -s命令报错“File exists”
现象:ln -s ... S99mytest提示S99mytest: File exists。
原因:你可能不小心重复执行了命令,或者之前已经创建过同名链接。
解决方案:直接删除旧链接,再重新创建:
sudo rm -f /etc/rc5.d/S99mytest sudo ln -s /etc/init.d/mytest.sh /etc/rc5.d/S99mytest问题4:重启后脚本依然没运行
现象:reboot后,/tmp下没有标记文件。
原因:runlevel命令显示的当前级别,未必是系统启动时的默认级别。有些系统(如Ubuntu)的默认目标是graphical.target,它可能映射到级别5,但也可能被覆盖。
解决方案:最保险的办法是,为所有常见的多用户级别都创建链接:
sudo ln -s /etc/init.d/mytest.sh /etc/rc2.d/S99mytest sudo ln -s /etc/init.d/mytest.sh /etc/rc3.d/S99mytest sudo ln -s /etc/init.d/mytest.sh /etc/rc4.d/S99mytest sudo ln -s /etc/init.d/mytest.sh /etc/rc5.d/S99mytest这样,无论系统默认启动到哪个级别,你的脚本都会被加载。
6. 总结与进阶思考
到这里,你已经亲手完成了一个Linux开机启动项的全部创建流程。从编写一个结构规范的脚本,到确认系统运行级别,再到创建精准的软链接,最后通过安全的telinit命令验证效果——每一步都清晰、可控、可复现。这不仅仅是一个“测试脚本”的部署,更是你深入理解Linux系统初始化机制的一次实战演练。
回顾整个过程,有三个核心要点值得你牢牢记住:
- 脚本是基石:一个合格的启动脚本,必须有明确的
start/stop/status接口和正确的注释头。它不是一段随意的Shell代码,而是一个被系统“认可”的服务单元。 - 链接是桥梁:
/etc/rcx.d/目录下的软链接,是连接你的脚本与系统启动流程的唯一通道。名字中的S/K和数字,决定了它何时、以何种顺序被调用。 - 验证是关键:永远不要跳过验证环节。
telinit命令提供了一种零风险的测试方式,比盲目重启高效得多。
当然,这只是一个起点。当你熟悉了这套基础机制后,可以自然地向更现代、更强大的方向演进。比如,学习如何编写systemd的.service文件,它提供了更精细的依赖管理、资源限制和日志集成;或者探索cron @reboot这种轻量级替代方案,适合那些不需要完整服务生命周期管理的简单任务。
但无论如何,今天你掌握的这套基于init.d和rcx.d的方法,依然是Linux世界中最经典、最通用、最值得信赖的启动机制之一。它不炫技,不依赖新特性,却能在任何一台标准Linux服务器上稳定运行数十年。
现在,是时候让你的脚本,在每一次系统苏醒时,都准时亮起它的第一盏灯了。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。