You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

问题分析

原脚本核心问题:

  1. 信号中断导致提前退出:第一个子进程发送信号时,主进程的wait命令会被中断,脚本直接执行后续代码并退出,剩余子进程醒来后找不到主进程,触发kill错误。
  2. 信号传递状态不可靠:信号无法携带复杂状态,多子进程同时发信号时,陷阱处理可能被覆盖,导致结果统计错误。

推荐修正方案:基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 19:55:59