子Shell信号捕获的常见误区
很多开发者在处理Bash子Shell信号时容易陷入一个误区:认为在子Shell中设置的trap会覆盖父Shell的配置。实际上,每个子Shell都拥有独立的信号处理环境,但需要注意作用域问题。
# 错误示例:变量作用域问题
(
trap "echo '子Shell捕获'" SIGTERM
sleep 10 &
child_pid=$! # 这里的变量在trap执行时可能已失效
)
正确的子Shell信号处理方案
要实现子Shell的独立信号处理,关键是要确保:
- 使用全局变量或特殊方式传递进程ID
- 避免在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)处理并发控制