병렬 백그라운드 프로세스(서브셸)의 종료 코드 수집

병렬 백그라운드 프로세스(서브셸)의 종료 코드 수집

다음과 같은 bash 스크립트가 있다고 가정해 보겠습니다.

echo "x" &
echo "y" &
echo "z" &
.....
echo "Z" &
wait

서브쉘/하위 프로세스의 종료 코드를 수집하는 방법이 있습니까? 이 작업을 수행할 방법을 찾고 있지만 아무것도 찾을 수 없습니다.이 서브셸을 병렬로 실행해야 합니다. 그렇지 않으면 더 쉬울 것입니다.

나는 ~을 찾고 있다일반적인해결책(병렬로 실행되는 알 수 없는/동적 개수의 하위 프로세스가 있습니다).

답변1

사용waitPID를 사용하는 경우할 것이다:

각 프로세스 ID로 지정된 하위 프로세스를 기다립니다.PID또는 작업 사양작업 사양기다렸던 마지막 명령의 종료 상태를 종료하고 반환합니다.

항상 각 프로세스의 PID를 저장해야 합니다.

echo "x" & X=$!
echo "y" & Y=$!
echo "z" & Z=$!

스크립트에서 작업 제어를 활성화 set -m하고 작업 사양을 사용할 수도 %n있지만 거의 원하지 않을 것입니다.직업 통제에는 다른 많은 부작용이 있다.

wait프로세스가 완료될 때와 동일한 코드가 반환됩니다. wait $Xas를 사용하여 미래의 (합리적인) 시점에 최종 코드에 액세스하거나 $?간단히 true/false로 사용할 수 있습니다 .

echo "x" & X=$!
echo "y" & Y=$!
...
wait $X
echo "job X returned $?"

wait명령이 아직 완료되지 않은 경우 명령이 완료될 때까지 일시 중지됩니다.

이런 침체를 피하고 싶다면,trap켜짐 으로 설정SIGCHLD, 종료 횟수를 계산하고 wait모두 완료되는 즉시 모든 작업을 처리합니다. wait거의 항상 혼자 사용할 수 있습니다 .

답변2

HandleJobs를 사용한 Alexander Mills의 답변은 좋은 출발점을 제공했지만 이 오류도 발생했습니다.

경고: 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

답변3

다른 답변은 불필요하게 복잡하다고 생각합니다.

배경 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[*]}"

반환 코드 수집같은 순서로직업의 도입과 함께.

하나 이상의 작업이 실패했는지 확인하려는 경우에는 모든 반환 코드를 수집할 필요가 없습니다.

error=false
for pid in ${pids[*]}; do
    if ! wait $pid; then
        error=true
    fi
done

답변4

명령을 식별하는 좋은 방법이 있는 경우 종료 코드를 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

tmp 파일을 삭제하는 것을 잊지 마십시오.

관련 정보