#!/bin/bash # 结构化 watch 脚本(按 structured-watch-plan 规则) # 用法:structured_watch.sh [task_label] # 规则:1m×5 / 5m×2 / 10m×2 / 30m×2 / 60m×2 + 结束自停 set -u PATTERN="${1:-app.entrypoints.cli}" LOG_FILE="${2:-/tmp/moneyflow_full.log}" OUT="${3:-/tmp/structured_watch.log}" LABEL="${4:-$PATTERN}" # 关键:pgrep -f 模式会**匹配到 watch 自己**(watch 命令行里有完整 PATTERN), # 任务结束后 watch 还认为"还活着"不会退出。 # 解决:watch 用 `[p]ython` 风格 trick 排除自己(bash 进程没 'python' 字样), # 或者更直接:只匹配 python 进程。 WATCH_BASENAME=$(basename "$0") # 构造一个绝不会匹配 watch 自己的模式: # 如果原 PATTERN 含 "python" 就保持,否则前缀 [p]ython 让 watch 自身不匹配 case "$PATTERN" in *python*) PGREP_PATTERN="$PATTERN" ;; *) PGREP_PATTERN="[p]ython.*$PATTERN" ;; esac # 把输出重定向到 OUT,system 通知时我能读到 exec > "$OUT" 2>&1 # schedule: 秒数 + 标签 SCHEDULE_SEC=(60 60 60 60 60 300 300 600 600 1800 1800 3600 3600) SCHEDULE_LABELS=( "[1m 1/5]" "[1m 2/5]" "[1m 3/5]" "[1m 4/5]" "[1m 5/5]" "[5m 1/2]" "[5m 2/2]" "[10m 1/2]" "[10m 2/2]" "[30m 1/2]" "[30m 2/2]" "[60m 1/2]" "[60m 2/2]" ) echo "=== structured watch 启动 ===" echo " task label: $LABEL" echo " pgrep pattern (orig): $PATTERN" echo " pgrep pattern (safe): $PGREP_PATTERN" echo " log file: $LOG_FILE" echo " 启动时间: $(date)" echo for i in "${!SCHEDULE_SEC[@]}"; do sleep "${SCHEDULE_SEC[$i]}" LABEL_NOW="${SCHEDULE_LABELS[$i]}" if ! pgrep -f "$PGREP_PATTERN" > /dev/null 2>&1; then echo "=== checkpoint $LABEL_NOW: $(date) ===" echo " 任务已退出(pgrep 不到 '$PGREP_PATTERN'),停止 schedule" echo echo "=== 最后 30 行日志 ===" tail -30 "$LOG_FILE" 2>/dev/null echo echo "=== structured watch 自然结束:$(date) ===" exit 0 fi echo "=== checkpoint $LABEL_NOW: $(date) ===" echo " 进程数: $(pgrep -af "$PGREP_PATTERN" | wc -l)" echo " --- log tail ---" tail -10 "$LOG_FILE" 2>/dev/null echo done echo "=== structured watch 跑完 13 个 checkpoint: $(date) ===" echo " 任务可能还在跑(pgrep 仍能找到),继续等待 pgrep 失效触发最终通知" # 13 个 checkpoint 后再每秒 pgrep 检查一次(轻量),任务一结束就退出 while pgrep -f "$PGREP_PATTERN" > /dev/null 2>&1; do sleep 30 done echo "=== 任务最终退出: $(date) ===" tail -30 "$LOG_FILE" 2>/dev/null