收集并行后台进程(子 shell)的退出代码

收集并行后台进程(子 shell)的退出代码

假设我们有一个像这样的 bash 脚本:

echo "x" &
echo "y" &
echo "z" &
.....
echo "Z" &
wait

有没有办法收集子外壳/子进程的退出代码?正在寻找方法来做到这一点,但找不到任何东西。我需要并行运行这些子 shell,否则会更容易。

我正在寻找一个通用的解决方案(我有未知/动态数量的并行运行的子进程)。

答案1

使用wait带有 PID,其中将要:

等待每个进程ID指定的子进程PID或工作规范工作规范退出并返回等待的最后一个命令的退出状态。

您需要随时保存每个进程的 PID:

echo "x" & X=$!
echo "y" & Y=$!
echo "z" & Z=$!

您还可以在脚本中启用作业控制set -m并使用%n作业规范,但您几乎肯定不想 -工作控制还有很多其他副作用

wait将返回与进程完成时相同的代码。您可以wait $X在以后的任何(合理的)时间点使用 as 来访问最终代码,$?或者简单地将其用作 true/false:

echo "x" & X=$!
echo "y" & Y=$!
...
wait $X
echo "job X returned $?"

wait如果命令尚未完成,则会暂停直到命令完成。

如果你想避免这样的停滞,你可以设置为trapSIGCHLD,计算终止次数,并wait在它们全部完成后立即处理所有 s。您wait几乎可以一直单独使用。

答案2

Alexander Mills 使用handleJobs 的答案给了我一个很好的起点,但也给了我这个错误

警告:run_pending_traps:trap_list[17] 中的值错误:0x461010

这可能是 bash 竞争条件问题

相反,我只是存储每个孩子的 pid,然后等待并专门获取每个孩子的退出代码。我发现这种更干净的方式是子进程在函数中生成子进程,并避免了等待父进程的风险,而我本想等待子进程。因为没有使用陷阱,所以发生的事情更清楚。

#!/usr/bin/env bash

# it seems it does not work well if using echo for function return value, and calling inside $() (is a subprocess spawned?) 
function wait_and_get_exit_codes() {
    children=("$@")
    EXIT_CODE=0
    for job in "${children[@]}"; do
       echo "PID => ${job}"
       CODE=0;
       wait ${job} || CODE=$?
       if [[ "${CODE}" != "0" ]]; then
           echo "At least one test failed with exit code => ${CODE}" ;
           EXIT_CODE=1;
       fi
   done
}

DIRN=$(dirname "$0");

commands=(
    "{ echo 'a'; exit 1; }"
    "{ echo 'b'; exit 0; }"
    "{ echo 'c'; exit 2; }"
    )

clen=`expr "${#commands[@]}" - 1` # get length of commands - 1

children_pids=()
for i in `seq 0 "$clen"`; do
    (echo "${commands[$i]}" | bash) &   # run the command via bash in subshell
    children_pids+=("$!")
    echo "$i ith command has been issued as a background job"
done
# wait; # wait for all subshells to finish - its still valid to wait for all jobs to finish, before processing any exit-codes if we wanted to
#EXIT_CODE=0;  # exit code of overall script
wait_and_get_exit_codes "${children_pids[@]}"

echo "EXIT_CODE => $EXIT_CODE"
exit "$EXIT_CODE"
# end

答案3

我认为其他答案是不必要的复杂。

如果您在启动后台 pid 时将其保存在数组中,则可以显式等待它们并在第二个数组中收集返回代码:

pids=()
echo "x" & pids+=($!)
echo "y" & pids+=($!)
echo "z" & pids+=($!)
....
echo "Z" & pids+=($!)

# wait and collect return codes
rets=()
for pid in ${pids[*]}; do
    wait $pid
    rets+=($?)
done
echo "Return codes: ${rets[*]}"

收集返回码以相同的顺序随着工作岗位的推出。

如果您只需要知道一项或多项作业是否失败,则无需收集所有返回代码:

error=false
for pid in ${pids[*]}; do
    if ! wait $pid; then
        error=true
    fi
done

答案4

如果您有一种识别命令的好方法,您可以将其退出代码打印到 tmp 文件,然后访问您感兴趣的特定文件:

#!/bin/bash

for i in `seq 1 5`; do
    ( sleep $i ; echo $? > /tmp/cmd__${i} ) &
done

wait

for i in `seq 1 5`; do # or even /tmp/cmd__*
    echo "process $i:"
    cat /tmp/cmd__${i}
done

不要忘记删除 tmp 文件。

相关内容