Как дождаться завершения в 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 ]

1 голос
/ 26 сентября 2013

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

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

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

1 голос
/ 05 ноября 2013

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

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

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

while ps -p $PID >/dev/null ; do sleep 0.1 ; done
1 голос
/ 25 июня 2015

Сигнал перехвата 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 !!!
1 голос
/ 02 октября 2018

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

#!/bin/bash
wait_for_pids()
{
    for (( i = 1; i <= $#; i++ )) do
        wait -n $@
        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» предназначен для несуществующего процесса, что означает, что ребенок мог завершиться.

0 голосов
/ 03 мая 2019

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

#wait for jobs
for job in `jobs -p`; do wait ${job}; done
0 голосов
/ 09 августа 2018

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

isProcessComplete(){
PID=$1
while [ -e /proc/$PID ]
do
    echo "Process: $PID is still running"
    sleep 5
done
echo "Process $PID has finished"
}
0 голосов
/ 16 августа 2018

Я думаю, что самый простой способ параллельно выполнять задания и проверять их состояние - использовать временные файлы. Уже есть пара похожих ответов (например, Ницше-жу и 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
0 голосов
/ 10 декабря 2008

Я думаю, может быть, запустите doCalculations; echo "$?" >> / tmp / acc в subshell , который отправляется в фоновый режим, затем ожидание, затем / 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
...