병렬 백그라운드 프로세스 (서브 쉘)의 종료 코드 수집 가정하십시오. echo “x” & echo

다음과 같이 bash 스크립트가 있다고 가정하십시오.

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

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

일반 솔루션을 찾고 있습니다 (알 수없는 / 동적 수의 하위 프로세스가 병렬로 실행됩니다).



답변

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


답변

waitPID와 함께 사용하면 다음 과 같은 이점 이 있습니다.

각 프로세스 ID pid 또는 작업 스펙 jobspec에 의해 지정된 하위 프로세스가 종료 될 때까지 기다렸다가 마지막으로 대기 한 명령의 종료 상태를 리턴하십시오.

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

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

스크립트에서 작업 제어를 set -m사용하고 %n작업 스펙을 사용할 수도 있지만 작업 제어를 원하지 않는 것은 다른 부작용이 많습니다 .

wait프로세스가 완료된 것과 동일한 코드를 반환합니다. 당신은 사용할 수 있습니다 wait $X로 최종 코드에 액세스 할 수있는 (합리적인) 나중에에 $?참 / 거짓으로하거나 사용 :

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

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

그렇게 멈추지 않으려면 on을 설정하고trapSIGCHLD 종료 수를 세고 wait모든 작업이 끝나면 모든 s를 한 번에 처리 할 수 ​​있습니다. wait거의 항상 혼자 사용 하는 것이 좋습니다.


답변

명령을 식별하는 좋은 방법이 있다면 종료 코드를 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 파일을 제거하는 것을 잊지 마십시오.


답변

다음을 사용하십시오 compound command-문장을 괄호 안에 넣으십시오.

( echo "x" ; echo X: $? ) &
( true ; echo TRUE: $? ) &
( false ; echo FALSE: $? ) &

출력을 줄 것이다

x
X: 0
TRUE: 0
FALSE: 1

여러 명령을 병렬로 실행하는 다른 방법은 GNU Parallel 을 사용하는 것 입니다. 실행할 명령 목록을 작성하여 파일에 넣으십시오 list.

cat > list
sleep 2 ; exit 7
sleep 3 ; exit 55
^D

모든 명령을 병렬로 실행하고 파일에서 종료 코드를 수집하십시오 job.log.

cat list | parallel -j0 --joblog job.log
cat job.log

출력은 다음과 같습니다.

Seq     Host    Starttime       JobRuntime      Send    Receive Exitval Signal  Command
1       :       1486892487.325       1.976      0       0       7       0       sleep 2 ; exit 7
2       :       1486892487.326       3.003      0       0       55      0       sleep 3 ; exit 55


답변

이것은 당신이 찾고있는 일반적인 스크립트입니다. 유일한 단점은 명령이 따옴표로 묶여 있다는 것입니다. IDE를 통한 구문 강조는 실제로 작동하지 않습니다. 그렇지 않으면 다른 답변을 시도했지만 이것이 가장 좋습니다. 이 답변에는 wait <pid>@Michael이 제공하는 사용 아이디어가 포함되어 있지만 trap가장 잘 작동 하는 명령을 사용하여 한 단계 더 나아갑니다 .

#!/usr/bin/env bash

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

function handleJobs() {
     for job in `jobs -p`; 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
}

trap 'handleJobs' CHLD  # trap command is the key part
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

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; # wait for all subshells to finish

echo "EXIT_CODE => $EXIT_CODE"
exit "$EXIT_CODE"
# end

올바른 길을 안내 해준 @michael homer에게 감사하지만, trap명령을 사용 하는 것이 AFAICT의 가장 좋은 방법입니다.


답변

@rolf의 또 다른 변형은 다음과 같습니다.

종료 상태를 저장하는 다른 방법은 다음과 같습니다.

mkdir /tmp/status_dir

그런 다음 각 스크립트를

script_name="${0##*/}"  ## strip path from script name
tmpfile="/tmp/status_dir/${script_name}.$$"
do something
rc=$?
echo "$rc" > "$tmpfile"

이를 통해 생성 한 스크립트의 이름과 프로세스 ID (동일한 스크립트의 둘 이상의 인스턴스가 실행중인 경우)를 포함하여 각 상태 파일의 고유 이름을 제공하여 나중에 참조 할 수 있도록 저장할 수 있습니다. 같은 위치에 있으므로 완료되면 전체 하위 디렉토리를 삭제할 수 있습니다.

다음과 같은 작업을 수행하여 각 스크립트에서 둘 이상의 상태를 저장할 수도 있습니다.

tmpfile="$(/bin/mktemp -q "/tmp/status_dir/${script_name}.$$.XXXXXX")"

이전과 같이 파일을 생성하지만 고유 한 임의 문자열을 추가합니다.

또는 동일한 파일에 더 많은 상태 정보를 추가 할 수 있습니다.


답변

script3경우에만 실행됩니다 script1script2성공과 script1script2병렬로 실행됩니다 :

./script1 &
process1=$!

./script2 &
process2=$!

wait $process1
rc1=$?

wait $process2
rc2=$?

if [[ $rc1 -eq 0 ]] && [[ $rc2 -eq 0  ]];then
./script3
fi