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


18

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

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

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

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


1
나는 당신이 원하는 것이 무엇인지 알아 내고 새로운 질문을하고, 당신이 찾고있는 행동 (의사 코드 또는 더 큰 예)을 정확하게 밝히려고 노력할 것입니다.
마이클 호머

3
나는 실제로 그 질문이 지금은 좋다고 생각합니다-나는 많은 수의 서브 프로세스를 가지고 있습니다. 모든 종료 코드를 수집해야합니다. 그게 다야.
Alexander Mills

답변:


6

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

시원 하지만, 명확성을 위해 생각 for job in "${childen[@]}"; do해야합니다for job in "${1}"; do
Alexander Mills

이 스크립트에서 내가 가진 유일한 관심사 children_pids+=("$!")는 실제로 하위 쉘에 대해 원하는 pid를 캡처하는 것입니다.
Alexander Mills

1
"$ {1}"(으)로 테스트했지만 작동하지 않습니다. 배열을 함수에 전달하고 있으며 분명히 bash에서 특별한주의가 필요합니다. $! 마지막으로 생성 된 작업의 pid입니다. tldp.org/LDP/abs/html/internalvariables.html을 참조하십시오. 테스트에서 올바르게 작동하는 것 같습니다. 이제 inRAID cache_dirs 스크립트에서 사용하고 있습니다. 그 직업. bash 4.4.12를 사용하고 있습니다.
arberg

좋은 예, 당신이 옳은 것 같습니다
Alexander Mills

20

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거의 항상 혼자 사용 하는 것이 좋습니다.


1
ughh, 죄송합니다.이 서브 쉘을 병렬로 실행해야합니다. 질문에 다음과 같이 지정할 것입니다.
Alexander Mills

신경 쓰지 마라. 어쩌면 이것은 내 설정에서 작동합니다 ... 코드에서 wait 명령이 어디서 시작됩니까? 나는 따르지 않는다
Alexander Mills

1
@AlexanderMills 그들은 병렬 실행됩니다. 변수 번호가 있으면 배열을 사용하십시오. (예를 들어 여기 에서 복제본 일 수 있음).
마이클 호머

예 감사합니다. 대기 명령이 귀하의 답변과 관련이 있다면 추가하십시오
Alexander Mills

당신 wait $X은 (합리적인) 나중 지점에서 실행 합니다.
마이클 호머

5

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


4

다음을 사용하십시오 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

고마워요, 이것을 생성하는 방법이 있습니까? 나는 단지 3 개의 하위 프로세스를 가지고 있지 않고 Z 하위 프로세스를 가지고 있습니다.
Alexander Mills

내가 일반적인 솔루션을 찾고 있어요 반영하도록 감사를 원래의 질문을 업데이트
알렉산더 밀스에게

그것을 생성하는 한 가지 방법은 루핑 구조를 사용하는 것일 수 있습니까?
Alexander Mills

루핑? 고정 된 명령 목록이 있거나 사용자가 제어하는 ​​명령입니까? 나는 당신이 무엇을하려고하는지 이해하지 못하지만 아마도 PIPESTATUS당신이 체크 아웃해야 할 것입니다. 이 seq 10 | gzip -c > seq.gz ; echo ${PIPESTATUS[@]}반환 0 0(첫 번째와 마지막 명령의 종료 코드).
hschou

네, 기본적으로 사용자가 제어
알렉산더 밀스

2

이것은 당신이 찾고있는 일반적인 스크립트입니다. 유일한 단점은 명령이 따옴표로 묶여 있다는 것입니다. 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의 가장 좋은 방법입니다.


1
SIGCHLD 트랩을 사용하여 종료시 해당 상태를 인쇄하는 등의 하위 항목을 처리 할 수도 있습니다. 또는 진행 카운터 업데이트 : 함수를 선언 한 다음 "trap function_name CHLD"를 사용하십시오. "set -m"과 같은 비 대화식 쉘에서 옵션을 설정해야 할 수도 있습니다.
Chunko

1
또한 "wait -n"은 자식을 기다린 다음 $?에 해당 자식의 종료 상태를 반환합니까? 변하기 쉬운. 따라서 각각이 종료 될 때 진행률을 인쇄 할 수 있습니다. 그러나 CHLD 트랩을 사용하지 않으면 그러한 방식으로 일부 하위 엑시트가 누락 될 수 있습니다.
Chunko

@Chunko 감사합니다! 좋은 정보입니다. 아마도 가장 좋은 것으로 답변을 업데이트 할 수 있습니까?
Alexander Mills

@Chunko에게 감사합니다. 트랩이 더 잘 작동합니다. <pid>를 기다리면 실패했습니다.
Alexander Mills

트랩이있는 버전이없는 버전보다 나은 이유와 이유를 설명 할 수 있습니까? (나는 그것이 유익하지 않고 더 복잡하기 때문에 더 나쁘지 않으므로 더 나쁘다고 믿습니다.)
Scott

1

@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")"

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

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


1

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

./script1 &
process1=$!

./script2 &
process2=$!

wait $process1
rc1=$?

wait $process2
rc2=$?

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

AFAICT, 이것은 마이클 호머의 대답을 다시 해치지 않습니다 .
Scott
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.