This commit is contained in:
1
2026-01-23 21:53:54 +00:00
parent 5682ff2d42
commit eda9d955fe

View File

@@ -198,12 +198,10 @@ else
fi fi
#!/bin/bash #!/bin/bash
# ===== 优雅终止logic会话先等内部程序退出 → 再退screen===== # ===== 优雅终止logic会话解决screen -ls卡住问题=====
echo "===== 优雅终止logic会话 =====" echo "===== 优雅终止logic会话 ====="
SCREEN_PID=""
# 替换为你实际的screen名称示例logic # 替换为你实际的screen名称示例logic
SCREEN_NAME="%s{screen_name}" SCREEN_NAME="logic"
# 日志文件路径(可根据需要调整)
LOG_FILE="./screen_logic_exit.log" LOG_FILE="./screen_logic_exit.log"
# 调试开关如需详细日志取消set -x注释 # 调试开关如需详细日志取消set -x注释
@@ -211,20 +209,17 @@ set -o pipefail
export PS4='[DEBUG] ${BASH_SOURCE}:${LINENO} - ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' export PS4='[DEBUG] ${BASH_SOURCE}:${LINENO} - ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
# set -x # set -x
# ========== 核心函数基于kill -0的等待进程退出函数 ========== # ========== 核心函数(保留你的原版) ==========
# 等待进程结束带60秒超时用kill -0检测存活输出进度点
wait_for_process_exit() { wait_for_process_exit() {
local pidKilled=$1 local pidKilled=$1
local begin=$(date +%s) # 记录开始时间(秒) local begin=$(date +%s)
local end local end
local timeout=60 # 最大等待时间(秒) local timeout=60
# 循环检测进程是否存活
while kill -0 $pidKilled > /dev/null 2>&1; do while kill -0 $pidKilled > /dev/null 2>&1; do
echo -n "." # 输出进度点,直观显示等待过程 echo -n "."
sleep 1; sleep 1;
# 检查是否超时
end=$(date +%s) end=$(date +%s)
if [ $((end - begin)) -gt $timeout ]; then if [ $((end - begin)) -gt $timeout ]; then
echo -e "\n⚠ 等待进程$pidKilled退出超时已等${timeout}秒)" echo -e "\n⚠ 等待进程$pidKilled退出超时已等${timeout}秒)"
@@ -232,50 +227,57 @@ wait_for_process_exit() {
fi fi
done done
# 最终状态提示
if ! kill -0 $pidKilled > /dev/null 2>&1; then if ! kill -0 $pidKilled > /dev/null 2>&1; then
echo -e "\n✅ 进程$pidKilled已退出" echo -e "\n✅ 进程$pidKilled已退出"
fi fi
} }
# 定义检查PID是否存活的函数复用kill -0逻辑
pid_is_alive() { pid_is_alive() {
local pid=$1 local pid=$1
if [ -n "$pid" ] && kill -0 "$pid" > /dev/null 2>&1; then if [ -n "$pid" ] && kill -0 "$pid" > /dev/null 2>&1; then
return 0 # PID存活 return 0
else else
return 1 # PID不存在 return 1
fi fi
} }
# 定义获取screen会话下的所有子进程PID
get_inner_procs() { get_inner_procs() {
local screen_pid=$1 local screen_pid=$1
# 5秒超时避免pstree卡住
local procs=$(timeout 5 pstree -p "$screen_pid" 2>/dev/null | grep -oE '\([0-9]+\)' | tr -d '()' | grep -v "$screen_pid" | sort -u) local procs=$(timeout 5 pstree -p "$screen_pid" 2>/dev/null | grep -oE '\([0-9]+\)' | tr -d '()' | grep -v "$screen_pid" | sort -u)
# 兜底pstree失败时用pgrep按screen名称查找
if [ -z "$procs" ]; then if [ -z "$procs" ]; then
procs=$(pgrep -f "SCREEN -S $SCREEN_NAME" 2>/dev/null | grep -v "$screen_pid") procs=$(pgrep -f "SCREEN -S $SCREEN_NAME" 2>/dev/null | grep -v "$screen_pid")
fi fi
echo "$procs" echo "$procs"
} }
# 第一步提取screen主PID并校验 screen_send_cmd() {
SCREEN_PID=$(screen -ls "$SCREEN_NAME" | grep -oE '[0-9]+\.'"$SCREEN_NAME" | head -1 | cut -d. -f1) local cmd="$1"
if [ -z "$SCREEN_PID" ]; then local screen_full_id="$2"
echo " 未找到$SCREEN_NAME会话对应的PID跳过终止流程" # ^M需手动生成Ctrl+v+回车
# 去掉exit 1直接跳过后续逻辑不影响其他脚本执行 screen -S "$screen_full_id" -p 0 -X stuff "${cmd}^M"
else sleep 1
# ========== 仅当找到PID时才执行以下终止流程 ========== }
echo "✅ 找到$SCREEN_NAME主PID$SCREEN_PID"
# 第二步:导出【退出前】的实时日志(终端+文件双输出) # ========== 核心修复给screen -ls加超时避免卡住 ==========
echo "===== 检测screen会话5秒超时 ====="
# 关键修改给整个提取命令加5秒超时超时则直接设为空
SCREEN_FULL_ID=$(timeout 5 screen -ls 2>/dev/null | grep -E "[0-9]+\.$SCREEN_NAME" | grep -v "Dead\|Invalid" | head -1 | awk '{print $1}')
# 无论是否超时/卡住只要SCREEN_FULL_ID为空就直接走后续
if [ -z "$SCREEN_FULL_ID" ]; then
echo " 未找到$SCREEN_NAME会话或screen -ls执行超时直接执行后续脚本"
else
# 找到会话:执行终止逻辑
SCREEN_PID=$(echo "$SCREEN_FULL_ID" | cut -d. -f1)
echo "✅ 找到$SCREEN_NAME主PID$SCREEN_PID | 完整ID$SCREEN_FULL_ID"
# 导出退出前日志
echo -e "\n===== 【退出前】$SCREEN_NAME 内程序实时log =====" echo -e "\n===== 【退出前】$SCREEN_NAME 内程序实时log ====="
screen -S "$SCREEN_NAME" -p 0 -X hardcopy -h "$LOG_FILE" 2>/dev/null screen -S "$SCREEN_FULL_ID" -p 0 -X hardcopy -h "$LOG_FILE" 2>/dev/null
cat "$LOG_FILE" cat "$LOG_FILE"
# 第三步:给所有子进程发SIGTERM信号,并等待进程退出 # 子进程发SIGTERM并等待
echo -e "\n===== 开始给所有子进程发优雅退出信号(SIGTERM) =====" echo -e "\n===== 子进程发优雅退出信号(SIGTERM) ====="
INNER_PROCS=$(get_inner_procs "$SCREEN_PID") INNER_PROCS=$(get_inner_procs "$SCREEN_PID")
if [ -z "$INNER_PROCS" ]; then if [ -z "$INNER_PROCS" ]; then
echo " 未检测到$SCREEN_NAME下的子进程" echo " 未检测到$SCREEN_NAME下的子进程"
@@ -284,9 +286,9 @@ else
for pid in $INNER_PROCS; do for pid in $INNER_PROCS; do
if pid_is_alive "$pid"; then if pid_is_alive "$pid"; then
echo -n "📌 终止进程$pid并等待退出" echo -n "📌 终止进程$pid并等待退出"
kill -15 "$pid" # 发送SIGTERM优雅退出信号 kill -15 "$pid"
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
wait_for_process_exit "$pid" # 调用等待函数 wait_for_process_exit "$pid"
else else
echo "❌ 进程$pid发送SIGTERM失败" echo "❌ 进程$pid发送SIGTERM失败"
fi fi
@@ -296,54 +298,47 @@ else
done done
fi fi
# 验证子进程是否全部退出 # 验证子进程退出状态
echo -e "\n===== 验证子进程退出状态 =====" echo -e "\n===== 验证子进程退出状态 ====="
REMAIN_PROCS=$(get_inner_procs "$SCREEN_PID") REMAIN_PROCS=$(get_inner_procs "$SCREEN_PID")
INNER_PROC_EXIST=true
if [ -z "$REMAIN_PROCS" ]; then if [ -z "$REMAIN_PROCS" ]; then
INNER_PROC_EXIST=false
echo "✅ $SCREEN_NAME内部所有程序已退出" echo "✅ $SCREEN_NAME内部所有程序已退出"
else else
echo "⚠️ 仍有残留进程:$REMAIN_PROCS" echo "⚠️ 仍有残留进程:$REMAIN_PROCS"
fi fi
# 退出screen会话 # 投递exit命令退出screen
if [ "$INNER_PROC_EXIST" = false ]; then echo -e "\n===== 优雅退出screen会话 ====="
echo "内部程序已退出,开始退出$SCREEN_NAME会话..." echo "$SCREEN_NAME投递exit命令..."
# 尝试正常退出screen会话 screen_send_cmd "exit" "$SCREEN_FULL_ID"
if screen -S "$SCREEN_NAME" -X quit 2>/dev/null; then
echo "✅ $SCREEN_NAME会话已通过screen -X quit退出" # 等待并验证最终状态
else echo -n "等待screen会话自动退出"
echo " screen -X quit执行失败检查PID是否存活..." begin=$(date +%s)
# 仅当PID存活时执行kill while timeout 1 screen -ls "$SCREEN_NAME" 2>/dev/null | grep -q -E "[0-9]+\.$SCREEN_NAME"; do
if pid_is_alive "$SCREEN_PID"; then echo -n "."
echo "📌 PID $SCREEN_PID 仍存活尝试kill终止..." sleep 1
timeout 5 kill -15 "$SCREEN_PID" 2>/dev/null if [ $((date +%s - begin)) -gt 30 ]; then
if [ $? -eq 0 ]; then echo -e "\n⚠ 等待screen退出超时30秒"
echo "✅ 已给screen主进程$SCREEN_PID发送SIGTERM信号" break
else fi
echo "⚠️ kill $SCREEN_PID 失败" done
fi
else
echo " PID $SCREEN_PID 已不存在跳过kill操作"
fi
fi
sleep 2
else
echo "⚠️ 内部程序未完全退出跳过退出screen会话"
fi
# 最终验证
echo -e "\n===== 最终验证 =====" echo -e "\n===== 最终验证 ====="
if screen -ls "$SCREEN_NAME" 2>/dev/null | grep -q -E "[0-9]+\.$SCREEN_NAME"; then if timeout 1 screen -ls "$SCREEN_NAME" 2>/dev/null | grep -q -E "[0-9]+\.$SCREEN_NAME"; then
echo "❌ $SCREEN_NAME会话最终仍未退出" echo "❌ $SCREEN_NAME会话最终仍未退出"
else else
echo "✅ $SCREEN_NAME会话已完全退出" echo "✅ $SCREEN_NAME会话已完全退出"
fi fi
fi fi
# 后续脚本可以从这里继续执行,不受上述逻辑影响 # ========== 后续脚本:必执行,永不卡住 ==========
echo -e "\n===== 终止logic会话流程结束继续执行后续脚本 =====" echo -e "\n===== 终止logic会话流程结束继续执行后续脚本 ====="
# 示例后续逻辑
# echo "执行后续任务:备份日志、启动新进程等..."
# ===== 准备下载目录 ===== # ===== 准备下载目录 =====
echo "创建工作目录:%s{work_dir}" echo "创建工作目录:%s{work_dir}"