如何在Bash中实现后台函数向父进程发送信号?
Bash异步任务处理:批量后台测试并收集结果
需求:
- 多次以后台进程形式调用带不同参数的函数(模拟网络请求测试)
- 每个后台任务的退出码作为其运行结果
- 只要有一个测试返回非零,主脚本最终结果也为非零
原尝试脚本(无法正常工作)
#!/bin/bash declare -i all_tests_res=0 declare -r PASSED=0 declare -r FAILED=1 # function to be run as background job test_fn() { slp_time=$(( RANDOM % 10 + 1 )) # tests have different duration sleep "$slp_time" # long time running process imitations if (( RANDOM % 2 == 0 )); then # even is success, odd is fail kill -s USR1 "$PPID" # send signal to parent process by its PID else kill -s USR2 "$PPID" # send signal to parent process by its PID fi } # function to set result of whole script to non null if there is one failed test handler() { local res=$1 if (( all_tests_res==0 && res==1 )); then all_tests_res=1 fi } main() { for (( i=0; i<10; i++ )); do test_fn "$i" & done wait } # registreg traps for signals going from background process trap 'handler $PASSED' USR1 trap 'handler $FAILED' USR2 main echo "all_tests_res = $all_tests_res"
原脚本运行错误输出
$ ./bg-tests.sh ./bg-tests.sh: line 12: kill: (2957828) - No such process ./bg-tests.sh: line 14: kill: (2957828) - No such process ./bg-tests.sh: line 12: kill: (2957828) - No such process ./bg-tests.sh: line 12: kill: (2957828) - No such process ./bg-tests.sh: line 12: kill: (2957828) - No such process ./bg-tests.sh: line 12: kill: (2957828) - No such process ./bg-tests.sh: line 14: kill: (2957828) - No such process ./bg-tests.sh: line 12: kill: (2957828) - No such process ./bg-tests.sh: line 14: kill: (2957828) - No such process all_tests_res = 0
问题分析
原脚本核心问题:
- 信号中断导致提前退出:第一个子进程发送信号时,主进程的
wait命令会被中断,脚本直接执行后续代码并退出,剩余子进程醒来后找不到主进程,触发kill错误。 - 信号传递状态不可靠:信号无法携带复杂状态,多子进程同时发信号时,陷阱处理可能被覆盖,导致结果统计错误。
推荐修正方案:基于PID和wait收集退出码
改用更可靠的方式:记录每个后台进程的PID,逐个等待并获取退出码,统一统计结果。
修正后的脚本
#!/bin/bash declare -i all_tests_res=0 declare -r PASSED=0 declare -r FAILED=1 # 后台测试函数:直接返回退出码,无需信号 test_fn() { local test_id=$1 slp_time=$(( RANDOM % 10 + 1 )) echo "Test $test_id started, sleeping $slp_time seconds" sleep "$slp_time" # 模拟测试结果:随机返回成功/失败 if (( RANDOM % 2 == 0 )); then echo "Test $test_id PASSED" return $PASSED else echo "Test $test_id FAILED" return $FAILED fi } main() { local pids=() # 启动所有后台测试,记录PID for (( i=0; i<10; i++ )); do test_fn "$i" & pids+=("$!") # 记录最后一个后台进程的PID done # 逐个等待每个PID,收集退出码 for pid in "${pids[@]}"; do wait "$pid" local exit_code=$? # 只要有一个失败,就标记整体结果为失败 if (( exit_code != PASSED )); then all_tests_res=$FAILED fi done } main echo "=== Final Result ===" if (( all_tests_res == PASSED )); then echo "All tests passed" exit $PASSED else echo "Some tests failed" exit $FAILED fi
关键知识点解析
- 记录后台PID:
$!表示最后一个启动的后台进程的PID,存入数组可跟踪所有后台任务。 - wait获取退出码:
wait $pid会等待指定PID的进程结束,$?获取该进程的退出码。 - 可靠结果统计:逐个处理每个进程的退出码,避免信号中断导致的统计遗漏,也规避了信号传递的不可靠性。
信号机制学习版修正
如果要学习信号处理机制,可调整如下(仅用于学习,生产环境不推荐):
#!/bin/bash declare -i all_tests_res=0 declare -r PASSED=0 declare -r FAILED=1 test_fn() { local test_id=$1 slp_time=$(( RANDOM % 10 + 1 )) echo "Test $test_id started, sleeping $slp_time seconds" sleep "$slp_time" if (( RANDOM % 2 == 0 )); then echo "Test $test_id PASSED" kill -s USR1 "$PPID" else echo "Test $test_id FAILED" kill -s USR2 "$PPID" fi } handler() { local res=$1 if (( res == FAILED )); then all_tests_res=$FAILED fi # 重新等待剩余后台进程,避免提前退出 wait } main() { for (( i=0; i<10; i++ )); do test_fn "$i" & done wait } # 注册陷阱,触发后继续等待剩余进程 trap 'handler $PASSED' USR1 trap 'handler $FAILED' USR2 main echo "all_tests_res = $all_tests_res" exit $all_tests_res
此版本中,陷阱处理函数会重新调用wait,确保主进程不会提前退出,但仍需注意:信号无法区分发送者,多进程同时发信号可能导致部分信号丢失。
内容的提问来源于stack exchange,提问作者yar
相关产品推荐
相关产品推荐

