IT story

백그라운드 프로세스의 종료 코드 가져 오기

hot-time 2020. 8. 2. 17:18
반응형

백그라운드 프로세스의 종료 코드 가져 오기


메인 Bourne 쉘 스크립트에서 호출 된 명령 CMD가 영원히 필요합니다.

다음과 같이 스크립트를 수정하고 싶습니다.

  1. 백그라운드 프로세스 ($ CMD &)로 CMD 명령을 병렬로 실행하십시오.
  2. 기본 스크립트에서 몇 초마다 생성 된 명령을 모니터링하는 루프가 있습니다. 루프는 또한 스크립트의 진행률을 나타내는 일부 메시지를 표준 출력에 반영합니다.
  3. 생성 된 명령이 종료되면 루프를 종료하십시오.
  4. 생성 된 프로세스의 종료 코드를 캡처하고보고하십시오.

누군가 이것을 달성하기 위해 나에게 포인터를 줄 수 있습니까?


1 : bash $!에서 마지막으로 실행 된 백그라운드 프로세스의 PID를 보유합니다. 어쨌든 모니터링 할 프로세스를 알려줍니다.

4 : wait <n>PID <n>있는 프로세스 가 완료 될 때까지 기다립니다 (프로세스 가 완료 될 때까지 차단되므로 프로세스가 완료 될 때까지 호출하지 않을 수 있음). 완료된 프로세스의 종료 코드를 리턴합니다.

2, 3 : ps또는 ps | grep " $! "프로세스가 여전히 실행 중인지 알 수 있습니다. 출력을 이해하고 마무리에 얼마나 가까운지를 결정하는 방법은 사용자에게 달려 있습니다. ( ps | grep멍청한 증거는 아닙니다. 시간이 있으면 프로세스가 여전히 실행 중인지 알 수있는보다 강력한 방법을 생각해 낼 수 있습니다).

스켈레톤 스크립트는 다음과 같습니다.

# simulate a long process that will have an identifiable exit code
(sleep 15 ; /bin/false) &
my_pid=$!

while   ps | grep " $my_pid "     # might also need  | grep -v grep  here
do
    echo $my_pid is still in the ps output. Must still be running.
    sleep 3
done

echo Oh, it looks like the process is done.
wait $my_pid
# The variable $? always holds the exit code of the last command to finish.
# Here it holds the exit code of $my_pid, since wait exits with that code. 
my_status=$?
echo The exit status of the process was $my_status

이것은 비슷한 요구가있을 때 그것을 해결 한 방법입니다.

# Some function that takes a long time to process
longprocess() {
        # Sleep up to 14 seconds
        sleep $((RANDOM % 15))
        # Randomly exit with 0 or 1
        exit $((RANDOM % 2))
}

pids=""
# Run five concurrent processes
for i in {1..5}; do
        ( longprocess ) &
        # store PID of process
        pids+=" $!"
done

# Wait for all processes to finnish, will take max 14s
for p in $pids; do
        if wait $p; then
                echo "Process $p success"
        else
                echo "Process $p fail"
        fi
done

내가 본 것처럼 거의 모든 답변은 외부 유틸리티 (주로 ps)를 사용 하여 백그라운드 프로세스의 상태를 폴링합니다. SIGCHLD 신호를 포착하는 더 많은 유닉스 솔루션이 있습니다. 신호 처리기에서 중지 된 하위 프로세스를 확인해야합니다. 그것은하여 수행 할 수 있습니다 kill -0 <PID>(보편적 인) 내장 또는 존재 확인 /proc/<PID>디렉토리 (리눅스 특정을) 또는 사용하여 jobs내장 ( 특정. jobs -l또한 PID를보고이 경우 출력의 세번째 필드가 중지 될 수 있습니다. | 실행을 | 완료 | 종료.).

여기 내 예가 있습니다.

시작된 프로세스를이라고 loop.sh합니다. -x인수 또는 숫자를 인수로 허용 합니다. 들어는 -x이 NUM * 5 초 대기 번호에 대한 종료 코드 1로 종료됩니다. 5 초마다 PID를 인쇄합니다.

실행기 프로세스는 launch.sh다음과 같습니다.

#!/bin/bash

handle_chld() {
    local tmp=()
    for((i=0;i<${#pids[@]};++i)); do
        if [ ! -d /proc/${pids[i]} ]; then
            wait ${pids[i]}
            echo "Stopped ${pids[i]}; exit code: $?"
        else tmp+=(${pids[i]})
        fi
    done
    pids=(${tmp[@]})
}

set -o monitor
trap "handle_chld" CHLD

# Start background processes
./loop.sh 3 &
pids+=($!)
./loop.sh 2 &
pids+=($!)
./loop.sh -x &
pids+=($!)

# Wait until all background processes are stopped
while [ ${#pids[@]} -gt 0 ]; do echo "WAITING FOR: ${pids[@]}"; sleep 2; done
echo STOPPED

자세한 설명은 bash 스크립트에서 프로세스를 시작하지 못했습니다를 참조하십시오.


#/bin/bash

#pgm to monitor
tail -f /var/log/messages >> /tmp/log&
# background cmd pid
pid=$!
# loop to monitor running background cmd
while :
do
    ps ax | grep $pid | grep -v grep
    ret=$?
    if test "$ret" != "0"
    then
        echo "Monitored pid ended"
        break
    fi
    sleep 5

done

wait $pid
echo $?

배경 자식 프로세스의 pid는 $에 저장됩니다 ! . 모든 하위 프로세스의 pid를 PIDS [] 와 같은 배열에 저장할 수 있습니다 .

wait [-n] [jobspec or pid …]

각 프로세스 ID pid 또는 작업 스펙 jobspec에 의해 지정된 하위 프로세스가 종료 될 때까지 기다렸다가 마지막으로 대기 한 명령의 종료 상태를 리턴하십시오. 작업 스펙이 제공되면 작업의 모든 프로세스가 대기합니다. 인수가 제공되지 않으면 현재 활성화 된 모든 하위 프로세스가 대기되고 리턴 상태는 0입니다. -n 옵션이 제공되면 wait는 작업이 종료 될 때까지 대기하고 종료 상태를 리턴합니다. jobspec 또는 pid가 쉘의 활성 하위 프로세스를 지정하지 않으면 리턴 상태는 127입니다.

wait 명령을 사용 하면 모든 하위 프로세스가 완료 될 때까지 기다릴 수 있습니다. 한편 $? 를 통해 각 하위 프로세스의 종료 상태를 얻을 수 있습니까? 상태를 STATUS []에 저장하십시오 . 그런 다음 상태에 따라 무언가를 할 수 있습니다.

다음 두 가지 솔루션을 시도했지만 제대로 실행됩니다. solution01 은 더 간결한 반면 solution02 는 약간 복잡합니다.

solution01

#!/bin/bash

# start 3 child processes concurrently, and store each pid into array PIDS[].
process=(a.sh b.sh c.sh)
for app in ${process[@]}; do
  ./${app} &
  PIDS+=($!)
done

# wait for all processes to finish, and store each process's exit code into array STATUS[].
for pid in ${PIDS[@]}; do
  echo "pid=${pid}"
  wait ${pid}
  STATUS+=($?)
done

# after all processed finish, check their exit codes in STATUS[].
i=0
for st in ${STATUS[@]}; do
  if [[ ${st} -ne 0 ]]; then
    echo "$i failed"
  else
    echo "$i finish"
  fi
  ((i+=1))
done

solution02

#!/bin/bash

# start 3 child processes concurrently, and store each pid into array PIDS[].
i=0
process=(a.sh b.sh c.sh)
for app in ${process[@]}; do
  ./${app} &
  pid=$!
  PIDS[$i]=${pid}
  ((i+=1))
done

# wait for all processes to finish, and store each process's exit code into array STATUS[].
i=0
for pid in ${PIDS[@]}; do
  echo "pid=${pid}"
  wait ${pid}
  STATUS[$i]=$?
  ((i+=1))
done

# after all processed finish, check their exit codes in STATUS[].
i=0
for st in ${STATUS[@]}; do
  if [[ ${st} -ne 0 ]]; then
    echo "$i failed"
  else
    echo "$i finish"
  fi
  ((i+=1))
done

나는 당신의 접근 방식을 약간 바꿀 것입니다. 명령이 아직 활성 상태인지 메시지를보고하는지 몇 초마다 확인하는 대신, 몇 초마다 명령이 여전히 실행 중임을보고 한 다음 명령이 완료되면 해당 프로세스를 종료하는 다른 프로세스를 갖습니다. 예를 들면 다음과 같습니다.

#! / bin / sh

cmd () {수면 5; 24 번 출구; }

cmd & # 장기 실행 프로세스 실행
pid = $! # pid 기록

# 명령이 여전히 실행 중임을 계속보고하는 프로세스를 생성합니다.
echo "$ (date) : $ pid가 여전히 실행 중입니다"; 잠을 1; 완료 및
echoer = $!

# 프로세스가 끝나면 기자를 죽이는 함정을 설정하십시오.
함정 'kill $ echoer'0

# 프로세스가 끝날 때까지 기다립니다
기다리는 경우 $ pid; 그때
    에코 "cmd 성공"
그밖에
    echo "cmd FAILED !! (반환 $?)"
fi

우리 팀은 원격 SSH 실행 스크립트와 동일한 요구 사항을 가지고 있었으며 25 분 동안 활동이 없으면 시간이 초과되었습니다. 다음은 모니터링 루프가 매초마다 백그라운드 프로세스를 확인하지만 10 분마다 인쇄하여 비활성 시간 초과를 억제하는 솔루션입니다.

long_running.sh & 
pid=$!

# Wait on a background job completion. Query status every 10 minutes.
declare -i elapsed=0
# `ps -p ${pid}` works on macOS and CentOS. On both OSes `ps ${pid}` works as well.
while ps -p ${pid} >/dev/null; do
  sleep 1
  if ((++elapsed % 600 == 0)); then
    echo "Waiting for the completion of the main script. $((elapsed / 60))m and counting ..."
  fi
done

# Return the exit code of the terminated background process. This works in Bash 4.4 despite what Bash docs say:
# "If neither jobspec nor pid specifies an active child process of the shell, the return status is 127."
wait ${pid}

위의 솔루션과 유사한 간단한 예입니다. 프로세스 출력을 모니터링 할 필요가 없습니다. 다음 예제는 tail을 사용하여 출력을 따릅니다.

$ echo '#!/bin/bash' > tmp.sh
$ echo 'sleep 30; exit 5' >> tmp.sh
$ chmod +x tmp.sh
$ ./tmp.sh &
[1] 7454
$ pid=$!
$ wait $pid
[1]+  Exit 5                  ./tmp.sh
$ echo $?
5

Use tail to follow process output and quit when the process is complete.

$ echo '#!/bin/bash' > tmp.sh
$ echo 'i=0; while let "$i < 10"; do sleep 5; echo "$i"; let i=$i+1; done; exit 5;' >> tmp.sh
$ chmod +x tmp.sh
$ ./tmp.sh
0
1
2
^C
$ ./tmp.sh > /tmp/tmp.log 2>&1 &
[1] 7673
$ pid=$!
$ tail -f --pid $pid /tmp/tmp.log
0
1
2
3
4
5
6
7
8
9
[1]+  Exit 5                  ./tmp.sh > /tmp/tmp.log 2>&1
$ wait $pid
$ echo $?
5

Another solution is to monitor processes via the proc filesystem (safer than ps/grep combo); when you start a process it has a corresponding folder in /proc/$pid, so the solution could be

#!/bin/bash
....
doSomething &
local pid=$!
while [ -d /proc/$pid ]; do # While directory exists, the process is running
    doSomethingElse
    ....
else # when directory is removed from /proc, process has ended
    wait $pid
    local exit_status=$?
done
....

Now you can use the $exit_status variable however you like.


This may be extending beyond your question, however if you're concerned about the length of time processes are running for, you may be interested in checking the status of running background processes after an interval of time. It's easy enough to check which child PIDs are still running using pgrep -P $$, however I came up with the following solution to check the exit status of those PIDs that have already expired:

cmd1() { sleep 5; exit 24; }
cmd2() { sleep 10; exit 0; }

pids=()
cmd1 & pids+=("$!")
cmd2 & pids+=("$!")

lasttimeout=0
for timeout in 2 7 11; do
  echo -n "interval-$timeout: "
  sleep $((timeout-lasttimeout))

  # you can only wait on a pid once
  remainingpids=()
  for pid in ${pids[*]}; do
     if ! ps -p $pid >/dev/null ; then
        wait $pid
        echo -n "pid-$pid:exited($?); "
     else
        echo -n "pid-$pid:running; "
        remainingpids+=("$pid")
     fi
  done
  pids=( ${remainingpids[*]} )

  lasttimeout=$timeout
  echo
done

which outputs:

interval-2: pid-28083:running; pid-28084:running; 
interval-7: pid-28083:exited(24); pid-28084:running; 
interval-11: pid-28084:exited(0); 

참고 : 원하는 $pids경우 단순화하기 위해 배열 대신 문자열 변수로 변경할 수 있습니다 .


이 방법을 사용하면 스크립트는 백그라운드 프로세스를 기다릴 필요가 없으며 종료 상태에 대한 임시 파일 만 모니터하면됩니다.

FUNCmyCmd() { sleep 3;return 6; };

export retFile=$(mktemp); 
FUNCexecAndWait() { FUNCmyCmd;echo $? >$retFile; }; 
FUNCexecAndWait&

이제 스크립트는 retFile의 내용을 계속 모니터링 해야하는 동안 다른 작업을 수행 할 수 있습니다 (종료 시간과 같은 다른 정보도 포함 할 수 있음).

추신 : btw, 나는 bash로 사고를 코딩했다.

참고 URL : https://stackoverflow.com/questions/1570262/get-exit-code-of-a-background-process

반응형