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

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

Простой скрипт:

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

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

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

Ответы [ 28 ]

433 голосов
/ 10 декабря 2008

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
268 голосов
/ 05 февраля 2009

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
45 голосов
/ 16 февраля 2012

Если у вас установлен 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

39 голосов
/ 26 июня 2009

Вот что я придумала до сих пор. Я хотел бы увидеть, как прервать команду сна, если ребенок завершает работу, чтобы не пришлось настраивать 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 "$@"; do
      shift
      if kill -0 "$pid" 2>/dev/null; then
        debug "$pid is still alive."
        set -- "$@" "$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
37 голосов
/ 07 октября 2014

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

#!/bin/bash

pids=""

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

wait $pids

...code continued here ...

Обновление:

Как указывалось несколькими комментаторами, приведенное выше ожидает завершения всех процессов, прежде чем продолжить, но не завершается и не завершается ошибкой, если один из них завершается неудачно, это можно сделать с помощью следующей модификации, предложенной @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 ...
31 голосов
/ 16 марта 2016

Вот простой пример использования 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.
18 голосов
/ 13 июня 2013

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

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 в зависимости от желаемого количества параллелизма (0 означает «все сразу»).
  • GNU Parallel предлагает некоторые дополнительные функции при использовании вместо xargs - но он не всегда устанавливается по умолчанию.
  • В этом примере цикл for не является строго необходимым, поскольку echo $i в основном просто восстанавливает вывод $(whatever_list). Я просто думаю, что использование ключевого слова for немного облегчает понимание того, что происходит.
  • Обработка строк Bash может сбивать с толку - я обнаружил, что использование одинарных кавычек лучше всего подходит для упаковки нетривиальных сценариев.
  • Вы можете легко прервать всю операцию (используя ^ C или подобное), в отличие от более прямого подхода к параллелизму Bash .

Вот упрощенный рабочий пример ...

for i in {0..5} ; do echo $i ; done |xargs -I{} --max-procs 2 bash -c '
   {
   echo sleep {}
   sleep 2s
   }'
7 голосов
/ 25 февраля 2016

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

#! /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

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

7 голосов
/ 10 декабря 2008

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

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

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

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

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

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

5 голосов
/ 20 марта 2012

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

#!/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
...