🐧 万能 Linux 应用启停脚本
通用模板 + 保姆级讲解,新手也能秒会
适用场景:后端程序 / 二进制程序 / 各类服务先聊聊痛点:你是不是也遇到过这些问题?
- 部署程序要敲
nohup、&,日志重定向每次输错 - 重启服务找不到 PID,
ps -ef | grep一顿操作还可能杀错进程 - 程序启动后,不确定是否成功监听端口,也不知道资源占用情况
如果你中了任意一条,这个万能启停脚本就是你的救星!
脚本核心功能一览
自动获取绝对路径,任意目录执行不报错
一键启动:赋权 + 后台运行 + PID检测
一键停止:优雅关闭,超时强制终止
状态查看:精准判断程序占用情况
资源监控:CPU/内存占用详情一目了然
完整脚本代码(可直接复制)
#!/bin/bash
# 检查并安装依赖工具
check_dependencies() {
if ! command -v ps &> /dev/null || ! command -v awk &> /dev/null; then
echo "Error: ps/awk command not found, basic system tools missing!"
exit 1
fi
}
# 先检查依赖
check_dependencies
# 获取脚本所在目录的绝对路径
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
# ====================== 这里是【唯一需要修改】的配置区 ======================
APP_NAME="utool" # 应用名称,随便改,比如改成你的程序名
APP_PATH="$SCRIPT_DIR/$APP_NAME" # 应用可执行文件路径
# ==========================================================================
# 定义PID文件路径,启动后自动写入进程PID,停止时读取该文件的PID,无需端口
PID_FILE="$SCRIPT_DIR/$APP_NAME.pid"
# 转换为规范的绝对路径(处理../ 避免路径错误)
APP_PATH=$(readlink -f "$APP_PATH")
# 从PID文件读取PID,并校验PID对应的进程是否真实运行(防止僵尸PID文件)
get_pid_by_file() {
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
# 校验PID是否有效:数字格式 + 进程是否存在
if [[ "$pid" =~ ^[0-9]+$ ]] && ps -p "$pid" > /dev/null 2>&1; then
echo "$pid"
return 0
fi
# PID文件存在但进程已死,清理无效PID文件
rm -f "$PID_FILE"
fi
echo ""
}
# 检查应用是否正在运行
is_running() {
local pid=$(get_pid_by_file)
if [ -n "$pid" ]; then
return 0 # 正在运行 (shell中0代表true)
fi
return 1 # 未运行 (shell中非0代表false)
}
# 查看进程资源占用情况
resources() {
if ! is_running; then
echo "$APP_NAME is not running"
return 0
fi
local pid=$(get_pid_by_file)
echo "Resource usage for $APP_NAME (PID: $pid):"
echo "----------------------------------------"
# 使用ps命令获取详细资源占用信息
# %cpu: CPU使用率, %mem: 内存使用率, vsz: 虚拟内存, rss: 物理内存(KB)
ps -p "$pid" -o %cpu,%mem,vsz,rss,cmd --no-headers
echo -e "\nMemory details (KB):"
echo "----------------------------------------"
# 显示更详细的内存使用信息
if [ -f "/proc/$pid/statm" ]; then
local statm=$(cat /proc/$pid/statm)
local size=$(echo $statm | awk '{print $1}') # 总程序大小
local resident=$(echo $statm | awk '{print $2}') # 驻留集大小
local share=$(echo $statm | awk '{print $3}') # 共享内存
echo "Total program size: $size"
echo "Resident set size: $resident"
echo "Shared memory: $share"
else
echo "Detailed memory info not available"
fi
}
# 启动服务 (启动后自动写入PID到文件)
start() {
if is_running; then
local pid=$(get_pid_by_file)
echo "$APP_NAME is already running (PID: $pid)"
return 0
fi
echo "Starting $APP_NAME ..."
if [ -f "$APP_PATH" ]; then
# 自动赋予执行权限,避免手动授权
if [ ! -x "$APP_PATH" ]; then
chmod +x "$APP_PATH"
fi
# nohup后台运行,日志静默输出到黑洞,不生成nohup.out文件
nohup "$APP_PATH" > /dev/null 2>&1 &
local pid=$!
# 核心变更3:将启动成功的进程PID写入本地PID文件
echo "$pid" > "$PID_FILE"
echo "$APP_NAME started successfully (PID: $pid, PID file: $PID_FILE)"
else
echo "Error: $APP_PATH not found!"
return 1
fi
}
# 停止服务 (从PID文件读取PID执行关闭,先优雅再强制)
stop() {
if ! is_running; then
echo "$APP_NAME is not running"
rm -f "$PID_FILE" # 清理残留的无效PID文件
return 0
fi
local pid=$(get_pid_by_file)
echo "Stopping $APP_NAME (PID: $pid)..."
# 先尝试优雅关闭(kill -15),给程序释放资源的时间
kill "$pid"
# 等待进程退出,最多等10秒
local count=0
while is_running; do
count=$((count + 1))
if [ $count -gt 10 ]; then
# 10秒后仍未退出,强制终止(kill -9)
echo "Force stopping $APP_NAME (kill -9 $pid)..."
kill -9 "$pid"
break
fi
sleep 1
done
# 停止成功后,删除PID文件
if ! is_running; then
rm -f "$PID_FILE"
echo "$APP_NAME stopped successfully, PID file deleted"
else
echo "Failed to stop $APP_NAME"
fi
}
# 重启服务
restart() {
stop
# 无需校验端口,直接启动即可
start
}
# 查看服务状态
status() {
if is_running; then
local pid=$(get_pid_by_file)
echo "$APP_NAME is running normally (PID: $pid)"
echo "PID file path: $PID_FILE"
else
if [ -f "$PID_FILE" ]; then
echo "$APP_NAME is not running, but invalid PID file exists (cleaned automatically)"
rm -f "$PID_FILE"
else
echo "$APP_NAME is not running, no PID file found"
fi
fi
}
# 显示帮助信息
usage() {
echo "Usage: $0 {start|stop|restart|status|resources}"
echo "Commands:"
echo " start - Start the $APP_NAME service"
echo " stop - Stop the $APP_NAME service"
echo " restart - Restart the $APP_NAME service"
echo " status - Check $APP_NAME running status"
echo " resources - Show resource usage of $APP_NAME"
exit 1
}
# 主逻辑:接收用户传入的命令参数
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
status)
status
;;
resources)
resources
;;
*)
usage
;;
esac
快速适配你的程序(3步搞定)
核心优势:99% 代码不用改,只改 2 行配置
步骤1:找到配置区
脚本中醒目标注的配置区域:
# ====================== 这里是【唯一需要修改】的配置区 ====================== APP_NAME="utool" # 应用名称 APP_PATH="$SCRIPT_DIR/$APP_NAME" # 程序路径 # ==========================================================================
步骤2:修改配置项
| 配置项 | 说明 | 示例 |
|---|---|---|
| APP_NAME | 自定义应用名称 | APP_NAME="myserver" |
| APP_PATH | 程序可执行文件路径 | APP_PATH="/usr/bin/myserver" |
步骤3:赋予执行权限
保存脚本为 appctl.sh,执行以下命令:
chmod +x appctl.sh
脚本使用方法(命令合集)
# 启动应用 ./appctl.sh start # 停止应用 ./appctl.sh stop # 重启应用 ./appctl.sh restart # 查看状态 ./appctl.sh status # 查看资源占用 ./appctl.sh resources
必学 Shell 知识点
知识点1:判断命令是否存在的黄金写法
使用 command -v 而非 which,兼容性更强:
if ! command -v lsof &> /dev/null; then
知识点2:Shell 的 true/false 反人类设定
与其他语言相反:0 = 成功/真,非0 = 失败/假
知识点3:nohup + & 后台运行终极方案
实现程序断开 SSH 后仍常驻后台:
nohup "$APP_PATH" > /dev/null 2>&1 &
知识点4:优雅关闭 vs 强制关闭
优先 kill PID(优雅释放资源),超时再用 kill -9 PID(强制终止)
知识点5:lsof 查看端口占用的神仙用法
精准查找监听指定端口的进程:
lsof -Pan -i tcp:$port -s tcp:LISTEN
总结
这个脚本是后端开发/运维的高效工具,核心优势:
- 通用:适配所有 Linux 发行版和二进制程序
- 省心:自动处理依赖、路径、权限等问题
- 规范:遵循大厂启停逻辑,安全可靠
- 易用:3 行配置即可上手,零学习成本
Linux 的精髓在于自动化脚本,用好这个模板,让你从重复操作中解放出来!