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

Ale*_*lls 29 shell bash shell-script subshell

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

echo "x" &
echo "y" &
echo "z" &
.....
echo "Z" &
wait
Run Code Online (Sandbox Code Playgroud)

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

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

Mic*_*mer 22

使用wait具有PID,这将:

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

您需要在执行过程中保存每个进程的 PID:

echo "x" & X=$!
echo "y" & Y=$!
echo "z" & Z=$!
Run Code Online (Sandbox Code Playgroud)

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

wait将返回与完成的过程相同的代码。您可以wait $X在任何(合理的)稍后使用以访问最终代码$?或简单地将其用作真/假:

echo "x" & X=$!
echo "y" & Y=$!
...
wait $X
echo "job X returned $?"
Run Code Online (Sandbox Code Playgroud)

wait 将暂停,直到命令完成(如果尚未完成)。

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


arb*_*erg 10

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
Run Code Online (Sandbox Code Playgroud)


Tob*_*bia 9

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

如果您在启动后台 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[*]}"
Run Code Online (Sandbox Code Playgroud)

返回代码的收集顺序与作业启动的顺序相同。

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

error=false
for pid in ${pids[*]}; do
    if ! wait $pid; then
        error=true
    fi
done
Run Code Online (Sandbox Code Playgroud)


Rol*_*olf 6

如果您有识别命令的好方法,您可以将它们的退出代码打印到 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
Run Code Online (Sandbox Code Playgroud)

不要忘记删除 tmp 文件。


hsc*_*hou 5

使用compound command- 将语句放在括号中:

( echo "x" ; echo X: $? ) &
( true ; echo TRUE: $? ) &
( false ; echo FALSE: $? ) &
Run Code Online (Sandbox Code Playgroud)

将给出输出

x
X: 0
TRUE: 0
FALSE: 1
Run Code Online (Sandbox Code Playgroud)

并行运行多个命令的一种真正不同的方法是使用GNU Parallel。列出要运行的命令并将它们放入文件中list

cat > list
sleep 2 ; exit 7
sleep 3 ; exit 55
^D
Run Code Online (Sandbox Code Playgroud)

并行运行所有命令并收集文件中的退出代码job.log

cat list | parallel -j0 --joblog job.log
cat job.log
Run Code Online (Sandbox Code Playgroud)

输出是:

Seq     Host    Starttime       JobRuntime      Send    Receive Exitval Signal  Command
1       :       1486892487.325       1.976      0       0       7       0       sleep 2 ; exit 7
2       :       1486892487.326       3.003      0       0       55      0       sleep 3 ; exit 55
Run Code Online (Sandbox Code Playgroud)