하위 프로세스가 코드! = 0으로 끝날 때 여러 하위 프로세스가 종료되고 종료 코드! = 0을 반환하기 위해 bash에서 대기하는 방법은 무엇입니까?


562

하위 프로세스 중 하나가 코드! = 0로 끝나는 경우 해당 스크립트에서 생성 된 여러 하위 프로세스가 종료 코드! = 0을 반환하도록 bash 스크립트에서 대기하는 방법

간단한 스크립트 :

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

위의 스크립트는 생성 된 10 개의 하위 프로세스를 모두 기다리지 만 종료 상태는 항상 0입니다 (참조 help wait). 이 스크립트를 수정하여 생성 된 하위 프로세스의 종료 상태를 발견하고 하위 프로세스 중 하나가 코드! = 0으로 끝나면 종료 코드 1을 반환하도록하려면 어떻게해야합니까?

서브 프로세스의 PID를 수집하는 것보다 더 나은 해결책이 있습니까? 순서대로 기다렸다가 종료 상태를 합산합니까?


1
wait -n현대 bash에서는 첫 번째 / 다음 명령이 완료되었을 때만 반환되도록 사용할 수 있도록 터치하여 크게 향상시킬 수 있습니다 .
찰스 더피

: 당신이 Bash를 이용한 테스트를 찾고 있다면,이 시도 github.com/sstephenson/bats을
알렉산더 밀스에게

2
BATS의 적극적인 개발은 github.com/bats-core/bats-core
Potherca

3
@CharlesDuffy wait -n에는 하나의 작은 문제가 있습니다. 남아있는 자식 작업이없는 경우 (일명 경쟁 조건) 실패한 자식 프로세스와 구별 할 수없는 0이 아닌 종료 상태 (실패)를 반환합니다.
drevicko 2016 년

5
@CharlesDuffy-당신은 훌륭한 통찰력을 가지고 있으며 그것을 공유함으로써 SO에게 거대한 서비스를 제공합니다. 내가 읽은 SO 게시물의 약 80 %가 광대 한 경험의 바다에서 온 의견에 멋진 작은 다이아몬드를 공유하게 한 것 같습니다. 많은 감사합니다!
Brett Holman

답변:


520

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

9
Weel, 모든 프로세스를 기다릴 것이기 때문에, 예를 들어 두 번째 프로세스가 이미 완료된 동안 첫 번째 프로세스를 기다리는 경우에는 중요하지 않습니다 (두 번째는 다음 반복에서 선택됩니다). wait (2)와 함께 C에서 사용하는 것과 동일한 접근 방식입니다.
Luca Tettamanti

7
아, 나는-다른 해석을 본다 :) 나는 " 하위 프로세스가 종료되면 즉시 종료 코드 1을 반환한다"는 의미로 질문을 읽었다 .
Alnitak

56
PID는 실제로 재사용 될 수 있지만 현재 프로세스의 하위 프로세스가 아닌 프로세스를 기다릴 수 없습니다 (이 경우 대기 실패).
tkokoszka

12
% n을 사용하여 n 번째 백그라운드 작업을 참조하고 %%를 사용하여 최신 작업을 참조 할 수도 있습니다.
conny

30
@Nils_M : 네 말이 맞아, 미안하다. : 그것은 같은 것이다 그래서 for i in $n_procs; do ./procs[${i}] & ; pids[${i}]=$!; done; for pid in ${pids[*]}; do wait $pid; done;권리를?
synack

284

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

103
jobs -p실행 상태에있는 서브 프로세스의 PID를 제공합니다. 프로세스 jobs -p가 호출 되기 전에 프로세스가 완료되면 프로세스를 건너 뜁니다 . 따라서 어떤 하위 프로세스가 전에 종료 jobs -p되면 해당 프로세스의 종료 상태가 손실됩니다.
tkokoszka

15
와우,이 답변은 최고 등급의 답변보다 훨씬 낫습니다. : /
e40

4
@ e40과 아래 답변이 더 좋습니다. 그리고 '(cmd; echo "$?">> "$ tmpfile")을 사용하여 각 명령을 실행하고이 대기를 사용한 다음 실패에 대한 파일을 읽는 것이 더 좋습니다. 또한 주석 출력. … 또는 그다지 신경 쓰지 않을 때이 스크립트를 사용하십시오.
HoverHell

이 답변이 허용 된 것보다
낫다고 덧붙이고 싶습니다.

2
@tkokoszka가 정확하려면 하위 프로세스의 PIDjobs -p제공하지 않고 GPID를 제공 합니다. 대기 로직은 어쨌든 작동하는 것처럼 보입니다. 그룹이 존재하면 항상 그룹에서 대기하고 그렇지 않은 경우 pid이지만 알고 있어야합니다. 구문은 PID 또는 GPID가 있는지 여부에 따라 달라집니다. 예 :kill -- -$GPIDkill $PID
Timo

58

다음은를 사용하는 간단한 예 wait입니다.

일부 프로세스를 실행하십시오.

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

그런 다음 wait명령으로 기다리십시오 .

$ wait < <(jobs -p)

또는 wait모두를 위해 (논쟁없이).

백그라운드의 모든 작업이 완료 될 때까지 기다립니다.

경우 -n옵션이 제공되어, 다음 작업에 대한 대기 종료 및 종료 상태를 반환합니다.

help waithelp jobs구문을 참조하십시오 .

그러나 단점은 마지막 ID의 상태에서만 반환되므로 각 하위 프로세스의 상태를 확인하여 변수에 저장해야한다는 것입니다.

또는 계산 기능을 사용하여 오류 발생시 (빈 또는 실패한 로그가있는) 일부 파일을 생성 한 다음 해당 파일이 존재하는지 확인

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

1
bash를 처음 사용하는 사람들의 경우 여기 예제의 두 가지 계산은 다음 sleep 20 && truesleep 20 && false같습니다. 즉, 함수로 대체하십시오. 이해하기 &&||, 실행 man bash및 유형 '/'(검색)를 '^ * 목록은'(정규식)는 다음 입력 : 사람의 설명을 아래로 스크롤 &&하고||
drevicko

1
시작시 'fail'파일이 존재하지 않는지 확인해야합니다 (또는 삭제). 응용 프로그램 ||에 따라 STDERR을 발견하지 못 하기 전에 '2> & 1'을 추가하는 것도 좋습니다 .
drevicko 2016 년

나는 이것을 좋아한다, 어떤 결점? 실제로 모든 하위 프로세스를 나열하고 일부 작업을 수행하려는 경우에만 해당됩니다 (예 : 신호를 보내면 pid를 부기하거나 작업을 반복하려고합니다. 끝날 때까지 기다려wait
xgwang

작업 -p가 호출되기 전에 실패한 작업의 종료 상태를 놓치게됩니다.
Erik Aronesty

50

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


1
감사! :하지만 당신은 내가 이후에 떨어졌다 "혼란"문제를 언급하는 것을 잊었다 unix.stackexchange.com/a/35953
nobar

1
이것은 훌륭한 도구처럼 보이지만 위의 작업은 doCalculations동일한 스크립트에 정의 된 함수 인 Bash 스크립트에서 그대로 작동한다고 생각하지 않습니다 (OP 가이 요구 사항에 대해 명확하지는 않았지만). 시도 할 때 parallel말합니다 /bin/bash: doCalculations: command not found( seq 0 9위 예제 에서는 10 번이라고 말합니다 ). 해결 방법 은 여기 를 참조 하십시오 .
nobar

3
또한 관심 분야 : 옵션을 xargs통해 동시에 작업을 시작할 수있는 기능이 -P있습니다. 에서 여기 : export -f doCalculations ; seq 0 9 |xargs -P 0 -n 1 -I{} bash -c "doCalculations {}". xargs에 대한 맨 페이지에 제한 이 나열되어 parallel있습니다.
nobar

그리고 doCalculations다른 스크립트 내부 환경 변수 (custom PATH등) 에 의존하는 경우 export시작하기 전에 명시 적으로 설정해야합니다 parallel.
nobar

4
@nobar 혼란은 일부 패키저가 사용자를 위해 일을 망치기 때문에 발생합니다. 사용하여 설치 wget -O - pi.dk/3 | sh하면 혼란이 없습니다. 패키지 관리자가 문제를 해결 한 경우 패키지 관리자에 문제를 제기하는 것이 좋습니다. 그들을 볼 병렬 변수와 함수 (참조 GNU에 대한 (수출 -f) 내 보내야 man parallel: gnu.org/software/parallel/...를 )
올레 단게를

46

어떻습니까?

#!/bin/bash

pids=""

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

wait $pids

...code continued here ...

최신 정보:

여러 의견 제시자가 지적한 것처럼 위의 과정은 계속하기 전에 모든 프로세스가 완료되기를 기다립니다.하지만 프로세스 중 하나가 실패하면 종료하지 않고 실패하지 않습니다. :

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

1
대기 매뉴얼 페이지에 따르면 여러 PID로 대기하면 마지막으로 대기 한 프로세스의 반환 값만 반환됩니다. 따라서 허용 된 답변 (댓글 참조)에 제안 된 것처럼 추가 루프가 필요하고 각 PID를 별도로 기다립니다.
Vlad Frolov

1
이 페이지의 다른 곳에서는 언급되지 않았으므로 루프가 다음과 같이 추가됩니다.for pid in $pids; do wait $pid; done
Bryan

1
@bisounours_tronconneuse 네, 그렇습니다. 참조 help wait-여러 ID wait를 사용하면 @ vlad-frolov가 위에서 말한 것처럼 마지막 코드의 종료 코드 만 반환합니다.
Sam Brightman

1
브라이언, @SamBrightman Ok. 나는 당신의 추천으로 그것을 수정했습니다.
patapouf_ai

4
이 솔루션에 대해 분명한 우려가있었습니다. 해당 프로세스 wait가 호출 되기 전에 주어진 프로세스가 종료되면 어떻게됩니까? 이것은 문제가되지 않습니다. wait이미 종료 된 프로세스에서 이미 종료 된 프로세스 wait상태로 즉시 종료됩니다. (감사합니다, bash작가들!)
Daniel Griscom

39

지금까지 내가 생각해 낸 내용은 다음과 같습니다. 아이가 종료되면 잠자기 명령을 중단하여 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

루프 내에서 프로세스가 시작되지 않으므로 WAITALL_DELAY를 건너 뛰거나 매우 낮게 설정할 수 있습니다. 너무 비싸지 않다고 생각합니다.
Marian

21

이것을 병렬화하려면 ...

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 {}
      }' 
   )
  • 한 프로세스에서 오류가 발생 하면 다른 프로세스를 방해하지 않지만 시퀀스에서 전체적으로 종료 코드가 0이 아닙니다 .
  • 특정 경우에 함수 및 변수 내보내기가 필요할 수도 있고 필요하지 않을 수도 있습니다.
  • --max-procs원하는 병렬 처리량을 기준으로 설정할 수 있습니다 ( 0"한 번에 모두"를 의미 함).
  • GNU Parallel 은 대신 사용되는 몇 가지 추가 기능을 제공 xargs하지만 항상 기본적으로 설치되는 것은 아닙니다.
  • for때문에 루프는이 예에서 꼭 필요한 것은 echo $i근본적으로 단지 출력을 재생한다 $(whatever_list). for키워드를 사용하면 현재 상황을 좀 더 쉽게 알 수 있다고 생각합니다 .
  • 배쉬 문자열 처리는 혼란 스러울 수 있습니다. 작은 따옴표를 사용하면 사소한 스크립트를 래핑하는 데 가장 효과적이라는 것을 알았습니다.
  • Bash 병렬 처리에 대한보다 직접적인 접근 방식과 달리 전체 작업 (^ C 또는 유사 항목 사용)을 쉽게 중단 할 수 있습니다 .

간단한 작업 예는 다음과 같습니다.

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


7

Bash의 내장 기능으로 가능하다고 생각하지 않습니다.

자녀가 퇴원하면 알림 받을 수 있습니다 .

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

그러나 신호 처리기에서 자녀의 종료 상태를 얻는 확실한 방법은 없습니다.

하위 상태를 얻는 것은 일반적으로 wait하위 레벨 POSIX API에서 기능 계열의 작업입니다 . 불행히도 Bash의 지원은 제한적입니다. 하나의 특정 자식 프로세스를 기다리거나 종료 상태를 얻을 수 있거나 모든 프로세스를 기다릴 수 있으며 항상 0 결과를 얻을 수 있습니다.

무엇을 할 불가능 표시하는 것과 같습니다 waitpid(-1)일까지를 차단 어떤 자식 프로세스로 돌아갑니다.


7

나는 여기에 열거 된 많은 좋은 예를 보았고 내 것을 던지기를 원했습니다.

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

서버 / 서비스를 병렬로 시작 / 중지하는 것과 매우 유사한 것을 사용하고 각 종료 상태를 확인합니다. 나를 위해 잘 작동합니다. 이것이 누군가를 돕기를 바랍니다!


Ctrl + CI로 중지하면 백그라운드에서 실행중인 프로세스가 여전히 표시됩니다.
karsten

2
@karsten-이것은 다른 문제입니다. bash를 사용한다고 가정하면 종료 조건 (Ctrl + C 포함)을 트랩하고 현재 및 모든 하위 프로세스를 사용하여 종료시킬 수 있습니다.trap "kill 0" EXIT
Phil

@ 필이 맞습니다. 이러한 프로세스는 백그라운드 프로세스이므로 상위 프로세스를 종료하면 모든 하위 프로세스가 실행됩니다. 필자의 예제는 Phil이 말한 것처럼 추가 할 수있는 신호를 포착하지 않습니다.
Jason Slobotski

6

이것은 내가 사용하는 것입니다.

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

5

다음 코드는 모든 계산이 완료 될 때까지 기다렸다가 doCalculation 중 하나 라도 실패하면 종료 상태 1을 반환 합니다.

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

5

예를 들어 파일과 같이 쉘에서 결과를 저장하십시오.

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

5

다음은 여러 pid에서 작동하고 실행 시간이 너무 길면 경고를 기록하고 실행이 주어진 값보다 오래 걸리면 하위 프로세스를 중지하는 내 버전입니다.

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

4

bash 4.2 이상을 사용할 수있는 경우 다음이 유용 할 수 있습니다. 연관 배열을 사용하여 작업 이름 및 해당 "코드"와 작업 이름 및 해당 pid를 저장합니다. 또한 작업이 많은 CPU 또는 I / O 시간을 소비하고 동시 작업 수를 제한하려는 경우 편리한 속도 제한 방법을 내장했습니다.

스크립트는 첫 번째 루프에서 모든 작업을 시작하고 두 번째 루프에서 결과를 사용합니다.

이것은 간단한 경우에 약간 과잉이지만 꽤 깔끔한 것을 허용합니다. 예를 들어, 각 작업에 대한 오류 메시지를 다른 연관 배열에 저장하고 모든 것이 완료된 후 인쇄 할 수 있습니다.

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

4

방금 프로세스를 백그라운드로 만들고 병렬화하기 위해 스크립트를 수정했습니다.

나는 bash와 ksh를 모두 사용하는 Solaris에서 몇 가지 실험을 수행하고 'wait'가 0이 아닌 경우 종료 상태를 출력하거나 PID 인수가 제공되지 않은 경우 0이 아닌 종료를 반환하는 작업 목록을 출력한다는 것을 발견했습니다. 예 :

세게 때리다:

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

Ksh :

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

이 출력은 stderr에 작성되므로 OPs 예제에 대한 간단한 솔루션은 다음과 같습니다.

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

그러나 이것은 tmp 파일 IMO보다 훨씬 유용하지 않습니다. 서브 쉘에서 "wait"를 실행하지 않는 동안 tmp 파일을 피하는 유용한 방법을 찾지 못했습니다.


3

나는 이것에 가서 다른 예제의 모든 최고의 부분을 결합했습니다. 이 스크립트는 실행됩니다 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

3
#!/bin/bash
set -m
for i in `seq 0 9`; do
  doCalculations $i &
done
while fg; do true; done
  • set -m 스크립트에서 fg & bg를 사용할 수 있습니다
  • fg마지막 프로세스를 포 그라운드로 두는 것 외에도 포 그라운드 프로세스와 동일한 종료 상태를 갖습니다.
  • while fgfg종료 상태가 0이 아닌 종료가 있으면 루핑이 중지 됩니다.

불행히도 백그라운드의 프로세스가 0이 아닌 종료 상태로 종료되는 경우를 처리하지 않습니다. 루프는 즉시 종료되지 않습니다. 이전 프로세스가 완료 될 때까지 기다립니다.


3

여기에 이미 많은 답변이 있지만 아무도 배열을 사용하도록 제안하지 않은 것 같습니다 ... 그래서 내가 한 일이 있습니다. 이는 미래에 일부에게 유용 할 수 있습니다.

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

3

이것은 @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


trap $? $$나를 위해 매번 종료 코드를 0으로 설정하고 PID를 현재 실행중인 bash 쉘로 설정하는 것 같습니다
inetknght

당신은 그것에 대해 절대 확신합니까? 그게 말이되는지 확실하지 않습니다.
Alexander Mills

2

함정은 당신의 친구입니다. 많은 시스템에서 ERR을 잡을 수 있습니다. 모든 명령 후에 EXIT 또는 DEBUG를 트랩하여 코드 조각을 수행 할 수 있습니다.

이것은 모든 표준 신호에 추가됩니다.


1
몇 가지 예를 들어 답을 자세히 설명해 주시겠습니까?
ϹοδεMεδιϲ

2
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에서 정상은 실패 스크립트 정지합니다.

expect1하위 작업이 실패 하면 반환 됩니다.


2

이 목적을 위해 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=("$@")
    [ ${#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 "$@"; 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 $?
}

용법

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

참고 문헌


1

나는 이것을 최근에 사용했다 (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

거기에서 쉽게 추정 할 수 있고 트리거 (파일을 터치하고 신호를 전송)를 가지며 해당 트리거에 응답하기 위해 카운팅 기준 (터치 된 파일 수 등)을 변경할 수 있습니다. 또는 0이 아닌 rc를 원하는 경우 save_status에서 잠금을 종료하십시오.


1

나는 이것을 필요로했지만 목표 프로세스는 현재 쉘의 자식 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

동시에 도착하면 일부 신호가 손실 될 수 있으므로 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

여러 하위 프로세스를 기다리는 중 하나가 0이 아닌 상태 코드로 종료되면 종료하는 솔루션은 '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'은 존재하지 않는 프로세스를위한 것으로, 하위 프로세스가 종료되었을 수 있습니다.


1

모든 작업을 기다렸다가 마지막 실패한 작업의 종료 코드를 리턴하십시오. 위의 솔루션과 달리 pid 저장이 필요하지 않습니다. 그냥 bg 떨어져 기다려.

function wait_ex {
    # this waits for all jobs and returns the exit code of the last failing job
    ecode=0
    while true; do
        wait -n
        err="$?"
        [ "$err" == "127" ] && break
        [ "$err" != "0" ] && ecode="$err"
    done
    return $ecode
}

이것은 "명령을 찾을 수 없음"(코드 127)이 아닌 한 실행 된 명령에서 첫 번째 오류 코드를 안정적으로 제공합니다.
drevicko

0

프로세스를 기다리기 전에 프로세스가 완료된 경우가있을 수 있습니다. 이미 완료된 프로세스를 기다리면 pid가이 셸의 자식이 아닌 것처럼 오류가 발생합니다. 이러한 경우를 피하기 위해 다음 기능을 사용하여 프로세스가 완료되었는지 여부를 찾을 수 있습니다.

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

0

작업을 병렬로 실행하고 상태를 확인하는 가장 직접적인 방법은 임시 파일을 사용하는 것이라고 생각합니다. 이미 몇 가지 유사한 답변이 있습니다 (예 : Nietzche-jou 및 mug896).

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

위 코드는 스레드 안전하지 않습니다. 위의 코드가 자체 코드와 동시에 실행되는 것이 우려되는 경우 fail. $$와 같이보다 고유 한 파일 이름을 사용하는 것이 좋습니다. 마지막 행은 "하위 프로세스 중 하나가 코드! = 0으로 끝나는 경우 종료 코드 1을 반환 하시겠습니까?"라는 요구 사항을 충족하는 것입니다. 나는 거기에 정리하기 위해 추가 요구 사항을 던졌습니다. 다음과 같이 작성하는 것이 더 명확했을 수 있습니다.

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

나는 jobs -pPID를 수집 하는 데 사용되는 함정에 거의 빠져 들었습니다 . 아래 스크립트에서 볼 수 있듯이 자식이 이미 종료 된 경우 작동하지 않습니다. 내가 선택한 솔루션은 단순히 wait -nN 번 호출하는 것이 었 습니다. 여기서 N은 내가 가진 자녀 수이며 결정적으로 알고 있습니다.

#!/usr/bin/env bash

sleeper() {
    echo "Sleeper $1"
    sleep $2
    echo "Exiting $1"
    return $3
}

start_sleepers() {
    sleeper 1 1 0 &
    sleeper 2 2 $1 &
    sleeper 3 5 0 &
    sleeper 4 6 0 &
    sleep 4
}

echo "Using jobs"
start_sleepers 1

pids=( $(jobs -p) )

echo "PIDS: ${pids[*]}"

for pid in "${pids[@]}"; do
    wait "$pid"
    echo "Exit code $?"
done

echo "Clearing other children"
wait -n; echo "Exit code $?"
wait -n; echo "Exit code $?"

echo "Waiting for N processes"
start_sleepers 2

for ignored in $(seq 1 4); do
    wait -n
    echo "Exit code $?"
done

산출:

Using jobs
Sleeper 1
Sleeper 2
Sleeper 3
Sleeper 4
Exiting 1
Exiting 2
PIDS: 56496 56497
Exiting 3
Exit code 0
Exiting 4
Exit code 0
Clearing other children
Exit code 0
Exit code 1
Waiting for N processes
Sleeper 1
Sleeper 2
Sleeper 3
Sleeper 4
Exiting 1
Exiting 2
Exit code 0
Exit code 2
Exiting 3
Exit code 0
Exiting 4
Exit code 0
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.