if [ $? -ne 0 ]; then
echo "错误: 无法关闭现有 Tomcat 实例,终止启动流程!" >&2
exit 1
fi
$?
是shell中的特殊变量,表示上一个命令的退出状态码-ne 0
表示"不等于0"(在Unix/Linux中,0通常表示成功,非0表示错误)- 如果前一个命令执行失败(返回非0),则执行花括号内的内容:
- 输出错误信息到标准错误输出(stderr)
- 使用exit 1终止脚本执行并返回错误状态码1
--------------------------------------------------------------------------------------------------
if [ -z "$1" ]; then
echo "用法: $0 <TOMCAT_DIRECTORY>"
exit 1
fi
具体解释如下:
if [ -z "$1" ]
检查第一个参数($1
)是否为空字符串-z
测试字符串长度是否为0$1
表示脚本的第一个参数
如果参数为空(即用户没有提供参数),则:
- 输出用法提示:
echo "用法: $0 <TOMCAT_DIRECTORY>"
$0
表示当前脚本的名称
- 以状态码1退出脚本:
exit 1
(1通常表示错误退出)
- 输出用法提示:
---------------------------------------------------------------------------------------------------
TOMCAT_DIR="$1"
TIMEOUT=10
TOMCAT=$(basename "${TOMCAT_DIR}")
CURRENT_USER=$(whoami)
以下是详细解释:
TOMCAT_DIR="$1"
- 将脚本的第一个参数(
$1
)赋值给变量TOMCAT_DIR
- 这个参数应该是Tomcat的安装目录路径
- 将脚本的第一个参数(
TIMEOUT=10
- 设置超时时间为10秒
- 用于后续等待Tomcat关闭的时间控制
TOMCAT=$(basename "${TOMCAT_DIR}")
- 使用
basename
命令从目录路径中提取最后一级目录名 - 例如:如果
TOMCAT_DIR
是/opt/apache-tomcat-9.0.45
,则TOMCAT
值为apache-tomcat-9.0.45
- 使用
CURRENT_USER=$(whoami)
- 获取当前执行脚本的用户名
- 用于后续的权限检查和日志记录
-------------------------------------------------------------------------------------------------------------
# 检查目录和脚本存在性
[ ! -d "${TOMCAT_DIR}" ] && echo "错误: 目录不存在" && exit 1
[ ! -x "${TOMCAT_DIR}/bin/shutdown.sh" ] && echo "错误: shutdown.sh 不可执行" && exit 1
# 获取 Tomcat 主进程 PID(精确匹配)
get_pids() {
ps -ef | grep "${TOMCAT}" | grep "org.apache.catalina.startup.Bootstrap" | grep -v grep | awk '{print $2}'
}
详细解释它们的功能和实现原理:
目录和脚本存在性检查部分:
[ ! -d "${TOMCAT_DIR}" ]
:检查指定的Tomcat目录是否存在-d
测试目录是否存在!
表示逻辑非
[ ! -x "${TOMCAT_DIR}/bin/shutdown.sh" ]
:检查shutdown.sh脚本是否存在且可执行-x
测试文件是否存在且可执行
- 如果任一检查失败,会输出错误信息并以状态码1退出
获取Tomcat进程PID的函数:
ps -ef
:列出所有进程的完整信息grep "${TOMCAT}"
:过滤包含Tomcat目录名的进程grep "org.apache.catalina.startup.Bootstrap"
:精确匹配Tomcat主类grep -v grep
:排除grep命令自身的进程awk '{print $2}'
:提取第二列(即PID)- 这个函数通过多级过滤确保只获取真正的Tomcat主进程PID
----------------------------------------------------------------------------------------------------------------
# 初始检查进程
PIDS=$(get_pids)
if [ -z "${PIDS}" ]; then
echo "Tomcat 未运行,无需关闭"
exit 0
fi
echo "检测到 Tomcat 进程 (PID: ${PIDS})"
# 步骤1:尝试优雅关闭(忽略非关键错误,但记录)
echo "尝试优雅关闭..."
if ! ${TOMCAT_DIR}/bin/shutdown.sh; then
echo "警告: shutdown.sh 执行失败(可能权限不足)"
fi
# 等待优雅关闭
echo "等待 ${TIMEOUT} 秒..."
sleep ${TIMEOUT}
详细解析:
初始进程检查:
- 调用
get_pids
函数获取Tomcat进程PID - 如果PID为空(
-z
测试),输出提示并正常退出(exit 0) - 否则显示检测到的进程PID
- 调用
优雅关闭流程:
- 首先尝试执行Tomcat的
shutdown.sh
脚本 - 使用
if ! command
结构捕获执行失败情况 - 即使失败也仅输出警告而不终止(因为后续还有强制关闭逻辑)
- 首先尝试执行Tomcat的
等待处理:
- 使用
sleep
命令等待预设的TIMEOUT
时间(之前定义为10秒) - 这是给Tomcat完成正常关闭流程的时间窗口
- 使用
----------------------------------------------------------------------------------------------------------
# 步骤2:检查是否仍有进程
PIDS=$(get_pids)
if [ -n "${PIDS}" ]; then
echo "尝试正常终止进程 (PID: ${PIDS})"
# 逐个检查并终止进程,记录失败
for pid in ${PIDS}; do
# 检查进程所有者
PROCESS_OWNER=$(ps -o user= -p "${pid}" 2>/dev/null || echo "unknown")
echo "进程 ${pid} 所有者: ${PROCESS_OWNER}"
# 尝试终止进程
if ! kill "${pid}" 2>/dev/null; then
echo "错误: 无法终止进程 ${pid}(权限不足,当前用户 ${CURRENT_USER} 无法操作 ${PROCESS_OWNER} 的进程)"
exit 1 # 权限不足时立即报错退出
fi
done
# 等待后再次检查
sleep 5
PIDS=$(get_pids)
if [ -n "${PIDS}" ]; then
echo "尝试强制终止进程 (PID: ${PIDS})"
for pid in ${PIDS}; do
if ! kill -9 "${pid}" 2>/dev/null; then
echo "错误: 无法强制终止进程 ${pid}(权限不足)"
exit 1 # 权限不足时立即报错退出
fi
done
fi
fi
# 最终检查
PIDS=$(get_pids)
if [ -z "${PIDS}" ]; then
echo "Tomcat 已成功关闭"
else
echo "错误: 仍有进程未关闭 (PID: ${PIDS})"
exit 1
fi
echo "========== 关闭完成 =========="
exit 0
详细解析:
进程二次检查:
- 使用
-n
测试检查是否仍有存活的Tomcat进程 - 如果有则进入强制终止流程
- 使用
分级终止策略:
- 先尝试普通kill命令(SIGTERM信号)
- 检查每个进程的所有者(使用
ps -o user= -p PID
) - 对kill失败的情况立即报错退出(权限问题)
强制终止阶段:
- 等待5秒后再次检查
- 对仍然存活的进程使用
kill -9
(SIGKILL信号) - 同样处理权限错误情况
最终状态确认:
- 最后一次检查进程状态
- 根据结果输出成功/失败信息
- 返回相应的退出状态码(0成功/1失败)
这个设计体现了完善的进程管理策略:
- 分级处理(先优雅后强制)
- 完善的错误检查和权限验证
- 明确的进程状态跟踪
- 清晰的执行反馈
代码中几个关键Shell技巧:
2>/dev/null
屏蔽错误输出ps -o user=
只输出用户名列|| echo "unknown"
错误处理- 多级
if [ -n/-z ]
条件测试