如何在Bash子Shell中独立设置信号捕获(trap)实现进程优雅退出


阅读 3 次

子Shell信号捕获的常见误区

很多开发者在处理Bash子Shell信号时容易陷入一个误区:认为在子Shell中设置的trap会覆盖父Shell的配置。实际上,每个子Shell都拥有独立的信号处理环境,但需要注意作用域问题。


# 错误示例:变量作用域问题
(
    trap "echo '子Shell捕获'" SIGTERM
    sleep 10 &
    child_pid=$!  # 这里的变量在trap执行时可能已失效
)

正确的子Shell信号处理方案

要实现子Shell的独立信号处理,关键是要确保:

  1. 使用全局变量或特殊方式传递进程ID
  2. 避免在trap中使用子Shell局部变量

#!/bin/bash

# 使用命名管道传递信号
temp_pipe=$(mktemp -u)
mkfifo "$temp_pipe"

(
    # 子Shell内部处理
    trap 'kill $child_pid 2>/dev/null; echo "子进程退出"; exit' SIGTERM
    sleep 30 &  # 模拟长时间运行的任务
    child_pid=$!
    wait
    
    # 清理管道
    rm -f "$temp_pipe"
) &

parent_pid=$!

# 父Shell控制逻辑
while true; do
    if read -t 1 signal < "$temp_pipe"; then
        case $signal in
            TERM)
                kill -TERM $parent_pid
                break
                ;;
        esac
    fi
    
    # 检查特殊条件
    if [ 特殊条件 ]; then
        kill -TERM $parent_pid
        break
    fi
done

实战案例:分布式任务控制

下面是一个完整的任务控制示例,展示了如何在子Shell中实现优雅退出:


#!/bin/bash

# 全局控制文件
CTRL_FILE="/tmp/subshell_ctrl.$$"

# 启动子Shell任务
(
    # 设置独立信号处理
    trap '[[ -f "$CTRL_FILE" ]] && rm -f "$CTRL_FILE"; exit 0' SIGTERM SIGINT
    
    # 模拟任务执行
    for i in {1..100}; do
        sleep 1
        # 检查控制文件是否存在
        [[ ! -f "$CTRL_FILE" ]] && continue
        
        # 读取控制命令
        cmd=$(cat "$CTRL_FILE")
        case "$cmd" in
            STOP)
                echo "收到停止指令"
                exit 0
                ;;
            PAUSE)
                echo "收到暂停指令"
                while [[ -f "$CTRL_FILE" && $(cat "$CTRL_FILE") == "PAUSE" ]]; do
                    sleep 1
                done
                ;;
        esac
    done
) &

# 存储子Shell PID
SUBSHELL_PID=$!

# 控制函数
function control_subshell {
    echo "$1" > "$CTRL_FILE"
}

# 示例控制命令
# control_subshell "STOP"  # 停止子Shell
# control_subshell "PAUSE" # 暂停子Shell

高级技巧:使用进程组

对于更复杂的场景,可以使用进程组来实现批量控制:


(
    # 设置新的进程组
    set -m
    trap 'kill -- -$$' EXIT
    
    # 启动多个子进程
    command1 &
    command2 &
    command3 &
    
    wait
)

常见问题排查

  • 信号未捕获:检查子Shell是否在后台运行(&)
  • 变量失效:确保trap中使用的变量是全局或特殊处理的
  • 竞争条件:使用文件锁(flock)处理并发控制