Как подождать в bash для нескольких подпроцессов для завершения и возврата кода выхода!= 0, когда какой-либо подпроцесс заканчивается кодом!= 0?

Как подождать в bash script для нескольких подпроцессов, порожденных из этого script, чтобы закончить и вернуть код выхода!= 0, когда какой-либо из подпроцессов заканчивается кодом!= 0?

Простой script:

#!/bin/bash
for i in `seq 0 9`; do
  doCalculations $i &
done
wait

Вышеупомянутый script будет ждать все 10 порожденных подпроцессов, но всегда будет давать статус выхода 0 (см. help wait). Как я могу изменить этот script, чтобы он обнаружил статусы выхода порожденных подпроцессов и возвращал код выхода 1, когда какой-либо из подпроцессов заканчивается кодом!= 0?

Есть ли лучшее решение для этого, чем сбор PID подпроцессов, ожидание их по порядку и суммирование статусов выхода?

Ответ 1

wait также (опционально) принимает PID процесса для ожидания и с $! Вы получаете PID последней команды, запущенной в фоновом режиме. Измените цикл, чтобы сохранить PID каждого порожденного подпроцесса в массиве, а затем снова выполните цикл ожидания каждого PID.

# run processes and store pids in array
for i in $n_procs; do
    ./procs[${i}] &
    pids[${i}]=$!
done

# wait for all pids
for pid in ${pids[*]}; do
    wait $pid
done

Ответ 2

http://jeremy.zawodny.com/blog/archives/010717.html:

#!/bin/bash

FAIL=0

echo "starting"

./sleeper 2 0 &
./sleeper 2 1 &
./sleeper 3 0 &
./sleeper 2 0 &

for job in `jobs -p`
do
echo $job
    wait $job || let "FAIL+=1"
done

echo $FAIL

if [ "$FAIL" == "0" ];
then
echo "YAY!"
else
echo "FAIL! ($FAIL)"
fi

Ответ 3

Если у вас установлен GNU Parallel, вы можете сделать:

# If doCalculations is a function
export -f doCalculations
seq 0 9 | parallel doCalculations {}

GNU Parallel даст вам код выхода:

  • 0 - все задания выполнены без ошибок.

  • 1-253 - Не удалось выполнить некоторые задания. Статус выхода дает количество неудачных заданий

  • 254 - более 253 рабочих мест не удалось.

  • 255 - Другая ошибка.

Посмотрите вступительные видео, чтобы узнать больше: http://pi.dk/1

Ответ 4

Вот простой пример, используя wait.

Запустите некоторые процессы:

$ sleep 10 &
$ sleep 10 &
$ sleep 20 &
$ sleep 20 &

Затем дождитесь их командой wait:

$ wait < <(jobs -p)

Или просто wait (без аргументов) для всех.

Это будет ждать завершения всех заданий в фоновом режиме.

Если включена опция -n, она ждет завершения следующего задания и возвращает его статус выхода.

Смотрите: help wait и help jobs для синтаксиса.

Однако недостатком является то, что он вернется только к статусу последнего идентификатора, поэтому вам нужно проверить статус для каждого подпроцесса и сохранить его в переменной.

Или сделайте свою вычислительную функцию для создания некоторого файла при сбое (пустой или с журналом сбоев), затем проверьте его, если существует, например

$ sleep 20 && true || tee fail &
$ sleep 20 && false || tee fail &
$ wait < <(jobs -p)
$ test -f fail && echo Calculation failed.

Ответ 5

Как насчет просто:

#!/bin/bash

pids=""

for i in `seq 0 9`; do
   doCalculations $i &
   pids="$pids $!"
done

wait $pids

...code continued here ...

Update:

Как указано несколькими комментаторами, вышеупомянутое ожидает завершения всех процессов до продолжения, но не выходит и не сработает, если один из них терпит неудачу, его можно сделать со следующей модификацией, предложенной @Bryan, @SamBrightman, и другие:

#!/bin/bash

pids=""
RESULT=0


for i in `seq 0 9`; do
   doCalculations $i &
   pids="$pids $!"
done

for pid in $pids; do
    wait $pid || let "RESULT=1"
done

if [ "$RESULT" == "1" ];
    then
       exit 1
fi

...code continued here ...

Ответ 6

Вот что я придумал до сих пор. Я хотел бы видеть, как прерывать команду sleep, если ребенок завершается, так что не нужно было бы настраивать WAITALL_DELAY на одно использование.

waitall() { # PID...
  ## Wait for children to exit and indicate whether all exited with 0 status.
  local errors=0
  while :; do
    debug "Processes remaining: $*"
    for pid in "[email protected]"; do
      shift
      if kill -0 "$pid" 2>/dev/null; then
        debug "$pid is still alive."
        set -- "[email protected]" "$pid"
      elif wait "$pid"; then
        debug "$pid exited with zero exit status."
      else
        debug "$pid exited with non-zero exit status."
        ((++errors))
      fi
    done
    (("$#" > 0)) || break
    # TODO: how to interrupt this sleep when a child terminates?
    sleep ${WAITALL_DELAY:-1}
   done
  ((errors == 0))
}

debug() { echo "DEBUG: $*" >&2; }

pids=""
for t in 3 5 4; do 
  sleep "$t" &
  pids="$pids $!"
done
waitall $pids

Ответ 7

Чтобы распараллелить это...

for i in $(whatever_list) ; do
   do_something $i
done

Перевести его на это...

for i in $(whatever_list) ; do echo $i ; done | ## execute in parallel...
   (
   export -f do_something ## export functions (if needed)
   export PATH ## export any variables that are required
   xargs -I{} --max-procs 0 bash -c ' ## process in batches...
      {
      echo "processing {}" ## optional
      do_something {}
      }' 
   )
  • Если в одном процессе произошла ошибка, она не будет прерывать другие процессы, но приведет к ненулевому коду выхода из последовательности в целом.
  • Экспорт функций и переменных может быть или не понадобиться в любом конкретном случае.
  • Вы можете установить --max-procs в зависимости от того, сколько parallelism вы хотите (0 означает "все одновременно" ).
  • GNU Parallel предлагает некоторые дополнительные функции при использовании вместо xargs - но он не всегда устанавливается по умолчанию.
  • В этом примере цикл for не является строго необходимым, так как echo $i в основном просто регенерирует вывод $(whatever_list). Я просто думаю, что использование ключевого слова for позволяет немного легче понять, что происходит.
  • Bash Обработка строк может сбивать с толку - я обнаружил, что использование одинарных кавычек лучше всего подходит для обертывания нетривиальных скриптов.
  • Вы можете легко прервать всю операцию (используя ^ C или аналогичную), в отличие от более прямого подхода к Bash parallelism.

Здесь приведен упрощенный рабочий пример...

for i in {0..5} ; do echo $i ; done |xargs -I{} --max-procs 2 bash -c '
   {
   echo sleep {}
   sleep 2s
   }'

Ответ 8

Я не думаю, что это возможно с Bash встроенной функциональностью.

Вы можете получать уведомление, когда ребенок выходит:

#!/bin/sh
set -o monitor        # enable script job control
trap 'echo "child died"' CHLD

Однако нет очевидного способа получить статус выхода ребенка в обработчике сигнала.

Получение этого дочернего статуса обычно является заданием семейства функций wait в API-интерфейсах POSIX более низкого уровня. К сожалению, поддержка Bash для этого ограничена - вы можете подождать одного конкретного дочернего процесса (и получить его статус выхода), или вы можете дождаться их всех и всегда получать результат 0.

То, что кажется невозможным, - это эквивалент waitpid(-1), который блокируется до тех пор, пока не вернется любой дочерний процесс.

Ответ 9

Я вижу много хороших примеров, приведенных здесь, хотел также выбросить мой.

#! /bin/bash

items="1 2 3 4 5 6"
pids=""

for item in $items; do
    sleep $item &
    pids+="$! "
done

for pid in $pids; do
    wait $pid
    if [ $? -eq 0 ]; then
        echo "SUCCESS - Job $pid exited with a status of $?"
    else
        echo "FAILED - Job $pid exited with a status of $?"
    fi
done

Я использую что-то очень похожее на запуск/остановку серверов/служб параллельно и проверку каждого статуса выхода. Отлично работает для меня. Надеюсь, это поможет кому-то!

Ответ 10

Следующий код будет ждать завершения всех вычислений и возврата статуса выхода 1, если какой-либо из doCalculations не удался.

#!/bin/bash
for i in $(seq 0 9); do
   (doCalculations $i >&2 & wait %1; echo $?) &
done | grep -qv 0 && exit 1

Ответ 11

Просто сохраните результаты из оболочки, например. в файле.

#!/bin/bash
tmp=/tmp/results

: > $tmp  #clean the file

for i in `seq 0 9`; do
  (doCalculations $i; echo $i:$?>>$tmp)&
done      #iterate

wait      #wait until all ready

sort $tmp | grep -v ':0'  #... handle as required

Ответ 12

Здесь моя версия, которая работает для нескольких pids, записывает предупреждения, если выполнение занимает слишком много времени, и останавливает подпроцессы, если выполнение занимает больше времени, чем заданное значение.

function WaitForTaskCompletion {
    local pids="${1}" # pids to wait for, separated by semi-colon
    local soft_max_time="${2}" # If execution takes longer than $soft_max_time seconds, will log a warning, unless $soft_max_time equals 0.
    local hard_max_time="${3}" # If execution takes longer than $hard_max_time seconds, will stop execution, unless $hard_max_time equals 0.
    local caller_name="${4}" # Who called this function
    local exit_on_error="${5:-false}" # Should the function exit program on subprocess errors       

    Logger "${FUNCNAME[0]} called by [$caller_name]."

    local soft_alert=0 # Does a soft alert need to be triggered, if yes, send an alert once 
    local log_ttime=0 # local time instance for comparaison

    local seconds_begin=$SECONDS # Seconds since the beginning of the script
    local exec_time=0 # Seconds since the beginning of this function

    local retval=0 # return value of monitored pid process
    local errorcount=0 # Number of pids that finished with errors

    local pidCount # number of given pids

    IFS=';' read -a pidsArray <<< "$pids"
    pidCount=${#pidsArray[@]}

    while [ ${#pidsArray[@]} -gt 0 ]; do
        newPidsArray=()
        for pid in "${pidsArray[@]}"; do
            if kill -0 $pid > /dev/null 2>&1; then
                newPidsArray+=($pid)
            else
                wait $pid
                result=$?
                if [ $result -ne 0 ]; then
                    errorcount=$((errorcount+1))
                    Logger "${FUNCNAME[0]} called by [$caller_name] finished monitoring [$pid] with exitcode [$result]."
                fi
            fi
        done

        ## Log a standby message every hour
        exec_time=$(($SECONDS - $seconds_begin))
        if [ $((($exec_time + 1) % 3600)) -eq 0 ]; then
            if [ $log_ttime -ne $exec_time ]; then
                log_ttime=$exec_time
                Logger "Current tasks still running with pids [${pidsArray[@]}]."
            fi
        fi

        if [ $exec_time -gt $soft_max_time ]; then
            if [ $soft_alert -eq 0 ] && [ $soft_max_time -ne 0 ]; then
                Logger "Max soft execution time exceeded for task [$caller_name] with pids [${pidsArray[@]}]."
                soft_alert=1
                SendAlert

            fi
            if [ $exec_time -gt $hard_max_time ] && [ $hard_max_time -ne 0 ]; then
                Logger "Max hard execution time exceeded for task [$caller_name] with pids [${pidsArray[@]}]. Stopping task execution."
                kill -SIGTERM $pid
                if [ $? == 0 ]; then
                    Logger "Task stopped successfully"
                else
                    errrorcount=$((errorcount+1))
                fi
            fi
        fi

        pidsArray=("${newPidsArray[@]}")
        sleep 1
    done

    Logger "${FUNCNAME[0]} ended for [$caller_name] using [$pidCount] subprocesses with [$errorcount] errors."
    if [ $exit_on_error == true ] && [ $errorcount -gt 0 ]; then
        Logger "Stopping execution."
        exit 1337
    else
        return $errorcount
    fi
}

# Just a plain stupid logging function to replace with yours
function Logger {
    local value="${1}"

    echo $value
}

Пример: подождите, пока все три процесса закончатся, запишите предупреждение, если выполнение занимает регистратор менее 5 секунд, остановите все процессы, если выполнение занимает более 120 секунд. Не выходите из программы при сбоях.

function something {

    sleep 10 &
    pids="$!"
    sleep 12 &
    pids="$pids;$!"
    sleep 9 &
    pids="$pids;$!"

    WaitForTaskCompletion $pids 5 120 ${FUNCNAME[0]} false
}
# Launch the function
someting

Ответ 13

Если у вас есть bash 4.2 или более поздняя версия, вам может быть полезно следующее. Он использует ассоциативные массивы для хранения имен задач и их "кода", а также имен задач и их значений. Я также разработал простой метод ограничения скорости, который может пригодиться, если ваши задачи потребляют много времени процессора или ввода-вывода, и вы хотите ограничить количество одновременных задач.

script запускает все задачи в первом цикле и потребляет результаты во втором.

Это немного избыточно для простых случаев, но это позволяет довольно аккуратный материал. Например, можно хранить сообщения об ошибках для каждой задачи в другом ассоциативном массиве и печатать их после того, как все опустилось.

#! /bin/bash

main () {
    local -A pids=()
    local -A tasks=([task1]="echo 1"
                    [task2]="echo 2"
                    [task3]="echo 3"
                    [task4]="false"
                    [task5]="echo 5"
                    [task6]="false")
    local max_concurrent_tasks=2

    for key in "${!tasks[@]}"; do
        while [ $(jobs 2>&1 | grep -c Running) -ge "$max_concurrent_tasks" ]; do
            sleep 1 # gnu sleep allows floating point here...
        done
        ${tasks[$key]} &
        pids+=(["$key"]="$!")
    done

    errors=0
    for key in "${!tasks[@]}"; do
        pid=${pids[$key]}
        local cur_ret=0
        if [ -z "$pid" ]; then
            echo "No Job ID known for the $key process" # should never happen
            cur_ret=1
        else
            wait $pid
            cur_ret=$?
        fi
        if [ "$cur_ret" -ne 0 ]; then
            errors=$(($errors + 1))
            echo "$key (${tasks[$key]}) failed."
        fi
    done

    return $errors
}

main

Ответ 14

Я только что модифицировал script для фона и распараллеливал процесс.

Я немного экспериментировал (в Solaris с bash и ksh) и обнаружил, что "wait" выводит статус выхода, если он не равен нулю, или список заданий, возвращающих ненулевой выход, когда аргумент PID не предоставляется, Например.

Bash:

$ sleep 20 && exit 1 &
$ sleep 10 && exit 2 &
$ wait
[1]-  Exit 2                  sleep 20 && exit 2
[2]+  Exit 1                  sleep 10 && exit 1

КШ:

$ sleep 20 && exit 1 &
$ sleep 10 && exit 2 &
$ wait
[1]+  Done(2)                  sleep 20 && exit 2
[2]+  Done(1)                  sleep 10 && exit 1

Этот вывод записывается в stderr, поэтому простым решением для примера OP может быть:

#!/bin/bash

trap "rm -f /tmp/x.$$" EXIT

for i in `seq 0 9`; do
  doCalculations $i &
done

wait 2> /tmp/x.$$
if [ `wc -l /tmp/x.$$` -gt 0 ] ; then
  exit 1
fi

Пока это:

wait 2> >(wc -l)

также вернет счет, но без файла tmp. Это также можно использовать таким образом, например:

wait 2> >(if [ `wc -l` -gt 0 ] ; then echo "ERROR"; fi)

Но это не намного полезнее, чем файл IMO tmp. Я не мог найти полезный способ избежать tmp файла, а также избегать запуска "wait" в подоболочке, которая вообще не работает.

Ответ 15

#!/bin/bash
set -m
for i in `seq 0 9`; do
  doCalculations $i &
done
while fg; do true; done
  • set -m позволяет использовать fg и bg в script
  • fg, в дополнение к последнему процессу на переднем плане, имеет тот же статус выхода, что и процесс, который он переднего плана
  • while fg прекратит цикл, когда любой fg завершает работу с ненулевым статусом выхода

к сожалению, это не будет обрабатывать случай, когда процесс в фоновом режиме выходит с ненулевым статусом выхода. (цикл не будет немедленно завершен, он будет ждать завершения предыдущих процессов.)

Ответ 16

У меня все получилось, и здесь собраны все лучшие части из других примеров. Этот script будет выполнять функцию checkpids, когда любой фоновый процесс завершается, и выдает статус выхода, не прибегая к опросу.

#!/bin/bash

set -o monitor

sleep 2 &
sleep 4 && exit 1 &
sleep 6 &

pids=`jobs -p`

checkpids() {
    for pid in $pids; do
        if kill -0 $pid 2>/dev/null; then
            echo $pid is still alive.
        elif wait $pid; then
            echo $pid exited with zero exit status.
        else
            echo $pid exited with non-zero exit status.
        fi
    done
    echo
}

trap checkpids CHLD

wait

Ответ 17

Здесь уже много ответов, но я удивлен, что никто, кажется, не предложил использовать массивы... Итак, вот что я сделал - это может быть полезно для некоторых в будущем.

n=10 # run 10 jobs
c=0
PIDS=()

while true

    my_function_or_command &
    PID=$!
    echo "Launched job as PID=$PID"
    PIDS+=($PID)

    (( c+=1 ))

    # required to prevent any exit due to error
    # caused by additional commands run which you
    # may add when modifying this example
    true

do

    if (( c < n ))
    then
        continue
    else
        break
    fi
done 


# collect launched jobs

for pid in "${PIDS[@]}"
do
    wait $pid || echo "failed job PID=$pid"
done

Ответ 18

Это работает, должно быть так же хорошо, если не лучше, чем ответ @HoverHell!

#!/usr/bin/env bash

set -m # allow for job control
EXIT_CODE=0;  # exit code of overall script

function foo() {
     echo "CHLD exit code is $1"
     echo "CHLD pid is $2"
     echo $(jobs -l)

     for job in 'jobs -p'; do
         echo "PID => ${job}"
         wait ${job} ||  echo "At least one test failed with exit code => $?" ; EXIT_CODE=1
     done
}

trap 'foo $? $$' CHLD

DIRN=$(dirname "$0");

commands=(
    "{ echo "foo" && exit 4; }"
    "{ echo "bar" && exit 3; }"
    "{ echo "baz" && exit 5; }"
)

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

for i in 'seq 0 "$clen"'; do
    (echo "${commands[$i]}" | bash) &   # run the command via bash in subshell
    echo "$i ith command has been issued as a background job"
done

# wait for all to finish
wait;

echo "EXIT_CODE => $EXIT_CODE"
exit "$EXIT_CODE"

# end

и, конечно, я увековечил этот сценарий в проекте NPM, который позволяет вам параллельно запускать команды bash, полезные для тестирования:

https://github.com/ORESoftware/generic-subshell

Ответ 19

Ловушка

- ваш друг. Вы можете использовать ERR во многих системах. Вы можете заблокировать EXIT или DEBUG, чтобы выполнить кусок кода после каждой команды.

Это в дополнение ко всем стандартным сигналам.

Ответ 20

set -e
fail () {
    touch .failure
}
expect () {
    wait
    if [ -f .failure ]; then
        rm -f .failure
        exit 1
    fi
}

sleep 2 || fail &
sleep 2 && false || fail &
sleep 2 || fail
expect

Верхняя часть set -e заставляет ваш script останавливаться при сбое.

expect вернет 1, если какой-либо подзаголовок не удался.

Ответ 21

Это то, что я использую:

#wait for jobs
for job in 'jobs -p'; do wait ${job}; done

Ответ 22

Я использовал это недавно (спасибо Алнитаку):

#!/bin/bash
# activate child monitoring
set -o monitor

# locking subprocess
(while true; do sleep 0.001; done) &
pid=$!

# count, and kill when all done
c=0
function kill_on_count() {
    # you could kill on whatever criterion you wish for
    # I just counted to simulate bash wait with no args
    [ $c -eq 9 ] && kill $pid
    c=$((c+1))
    echo -n '.' # async feedback (but you don't know which one)
}
trap "kill_on_count" CHLD

function save_status() {
    local i=$1;
    local rc=$2;
    # do whatever, and here you know which one stopped
    # but remember, you're called from a subshell
    # so vars have their values at fork time
}

# care must be taken not to spawn more than one child per loop
# e.g don't use `seq 0 9` here!
for i in {0..9}; do
    (doCalculations $i; save_status $i $?) &
done

# wait for locking subprocess to be killed
wait $pid
echo

Оттуда можно легко экстраполировать и иметь триггер (прикоснуться к файлу, отправить сигнал) и изменить критерии подсчета (прикоснитесь к файлам или что-то еще), чтобы ответить на этот триггер. Или, если вы просто хотите "any" non zero rc, просто убейте блокировку с save_status.

Ответ 23

Мне это нужно, но целевой процесс не был дочерним элементом текущей оболочки, и в этом случае wait $PID не работает. Вместо этого я нашел следующую альтернативу:

while [ -e /proc/$PID ]; do sleep 0.1 ; done

Это зависит от наличия procfs, который может быть недоступен (например, Mac не предоставляет его). Поэтому для переносимости вы можете использовать это вместо:

while ps -p $PID >/dev/null ; do sleep 0.1 ; done

Ответ 24

Треппинг сигнала CHLD может не работать, потому что вы можете потерять некоторые сигналы, если они пришли одновременно.

#!/bin/bash

trap 'rm -f $tmpfile' EXIT

tmpfile=$(mktemp)

doCalculations() {
    echo start job $i...
    sleep $((RANDOM % 5)) 
    echo ...end job $i
    exit $((RANDOM % 10))
}

number_of_jobs=10

for i in $( seq 1 $number_of_jobs )
do
    ( trap "echo job$i : exit value : \$? >> $tmpfile" EXIT; doCalculations ) &
done

wait 

i=0
while read res; do
    echo "$res"
    let i++
done < "$tmpfile"

echo $i jobs done !!!

Ответ 25

Решение для ожидания нескольких подпроцессов и выхода при выходе любого из них с ненулевым кодом состояния - использование wait -n

#!/bin/bash
wait_for_pids()
{
    for (( i = 1; i <= $#; i++ )) do
        wait -n [email protected]
        status=$?
        echo "received status: "$status
        if [ $status -ne 0 ] && [ $status -ne 127 ]; then
            exit 1
        fi
    done
}

sleep_for_10()
{
    sleep 10
    exit 10
}

sleep_for_20()
{
    sleep 20
}

sleep_for_10 &
pid1=$!

sleep_for_20 &
pid2=$!

wait_for_pids $pid2 $pid1

Код состояния "127" предназначен для несуществующего процесса, что означает, что ребенок мог выйти.

Ответ 26

Может быть случай, когда процесс завершен, прежде чем ждать его. Если мы инициируем ожидание процесса, который уже завершен, он вызовет ошибку, как pid не является дочерним элементом этой оболочки. Чтобы избежать таких случаев, можно использовать следующую функцию, чтобы определить, завершен ли процесс или нет:

isProcessComplete(){
PID=$1
while [ -e /proc/$PID ]
do
    echo "Process: $PID is still running"
    sleep 5
done
echo "Process $PID has finished"
}

Ответ 27

Я думаю, что самый простой способ параллельно выполнять задания и проверять их состояние - использовать временные файлы. Уже есть пара похожих ответов (например, Nietzche-jou и mug896).

#!/bin/bash
rm -f fail
for i in 'seq 0 9'; do
  doCalculations $i || touch fail &
done
wait 
! [ -f fail ]

Приведенный выше код не является потокобезопасным. Если вы обеспокоены тем, что приведенный выше код будет работать одновременно с самим собой, лучше использовать более уникальное имя файла, например fail. $$. Последняя строка должна выполнить требование: "вернуть код выхода 1, когда любой из подпроцессов заканчивается кодом! = 0?" Я бросил дополнительное требование, чтобы убрать. Возможно, было бы яснее написать это так:

#!/bin/bash
trap 'rm -f fail.$$' EXIT
for i in 'seq 0 9'; do
  doCalculations $i || touch fail.$$ &
done
wait 
! [ -f fail.$$ ] 

Вот аналогичный фрагмент для сбора результатов из нескольких заданий: я создаю временный каталог, записываю выходные данные всех подзадач в отдельный файл и затем выгружаю их для просмотра. Это не совсем соответствует вопросу - я добавляю его в качестве бонуса:

#!/bin/bash
trap 'rm -fr $WORK' EXIT

WORK=/tmp/$$.work
mkdir -p $WORK
cd $WORK

for i in 'seq 0 9'; do
  doCalculations $i >$i.result &
done
wait 
grep $ *  # display the results with filenames and contents

Ответ 28

Именно для этой цели я написал функцию bash под названием :for.

Примечание: :for не только сохраняет и возвращает код выхода из сбойной функции, но также завершает все параллельно работающие экземпляры. Который может не понадобиться в этом случае.

#!/usr/bin/env bash

# Wait for pids to terminate. If one pid exits with
# a non zero exit code, send the TERM signal to all
# processes and retain that exit code
#
# usage:
# :wait 123 32
function :wait(){
    local pids=("[email protected]")
    [ ${#pids} -eq 0 ] && return $?

    trap 'kill -INT "${pids[@]}" &>/dev/null || true; trap - INT' INT
    trap 'kill -TERM "${pids[@]}" &>/dev/null || true; trap - RETURN TERM' RETURN TERM

    for pid in "${pids[@]}"; do
        wait "${pid}" || return $?
    done

    trap - INT RETURN TERM
}

# Run a function in parallel for each argument.
# Stop all instances if one exits with a non zero
# exit code
#
# usage:
# :for func 1 2 3
#
# env:
# FOR_PARALLEL: Max functions running in parallel
function :for(){
    local f="${1}" && shift

    local i=0
    local pids=()
    for arg in "[email protected]"; do
        ( ${f} "${arg}" ) &
        pids+=("$!")
        if [ ! -z ${FOR_PARALLEL+x} ]; then
            (( i=(i+1)%${FOR_PARALLEL} ))
            if (( i==0 )) ;then
                :wait "${pids[@]}" || return $?
                pids=()
            fi
        fi
    done && [ ${#pids} -eq 0 ] || :wait "${pids[@]}" || return $?
}

usage

использованияfor.sh:

#!/usr/bin/env bash
set -e

# import :for from gist: https://gist.github.com/Enteee/c8c11d46a95568be4d331ba58a702b62#file-for
# if you don't like curl imports, source the actual file here.
source <(curl -Ls https://gist.githubusercontent.com/Enteee/c8c11d46a95568be4d331ba58a702b62/raw/)

msg="You should see this three times"

:(){
  i="${1}" && shift

  echo "${msg}"

  sleep 1
  if   [ "$i" == "1" ]; then sleep 1
  elif [ "$i" == "2" ]; then false
  elif [ "$i" == "3" ]; then
    sleep 3
    echo "You should never see this"
  fi
} && :for : 1 2 3 || exit $?

echo "You should never see this"
$ ./for.sh; echo $?
You should see this three times
You should see this three times
You should see this three times
1

Ссылки

Ответ 29

Я думаю, что, возможно, запустите doCalculations; echo "$?" >>/tmp/acc в подоболочке, которая отправляется в фоновый режим, затем wait, затем /tmp/acc будут содержать состояния выхода, по одному на строку. Я не знаю ни о каких последствиях множественных процессов, добавляющихся в файл аккумулятора.

Вот пробная версия этого предложения:

Файл: doCalcualtions

#!/bin/sh

random -e 20
sleep $?
random -e 10

Файл: попробуйте

#!/bin/sh

rm /tmp/acc

for i in $( seq 0 20 ) 
do
        ( ./doCalculations "$i"; echo "$?" >>/tmp/acc ) &
done

wait

cat /tmp/acc | fmt
rm /tmp/acc

Результат работы ./try

5 1 9 6 8 1 2 0 9 6 5 9 6 0 0 4 9 5 5 9 8