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


130

영원히 걸리는 내 메인 bourne 쉘 스크립트에서 호출 된 명령 CMD가 있습니다.

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

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

누군가 나에게 이것을 달성하기위한 지침을 줄 수 있습니까?


1
...그리고 승자는?
TrueY

1
@TrueY .. bob은 질문을 한 날 이후로 로그인하지 않았습니다. 우리는 알 것 같지 않습니다!
ghoti

답변:


128

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

15
ps -p $my_pid -o pid=둘 다 grep필요 하지 않습니다.
추후 공지가있을 때까지 일시 중지되었습니다.

54
kill -0 $!프로세스가 아직 실행 중인지 확인하는 더 좋은 방법입니다. 실제로 신호를 보내지 않고 외부 프로세스 대신 내장 된 셸을 사용하여 프로세스가 살아 있는지 확인합니다. AS는 man 2 kill"경우라고 SIG는 0이며, 다음 신호가 전송되지 않고, 오류 검사가 계속 수행되고,이 프로세스 ID 또는 프로세스 그룹 ID의 존재를 확인하기 위해 사용될 수있다."
ephemient

14
kill -0실행중인 프로세스에 신호를 보낼 수있는 권한이없는 경우 @ephemient 는 0이 아닌 값을 반환합니다. 불행히도이 1경우와 프로세스가 존재하지 않는 경우 모두 반환 됩니다. 이것은 당신이 프로세스를 소유하지 않는 유용 합니다-같은 도구 sudo가 관련되어 있거나 그들이 setuid (그리고 아마도 privs를 삭제 하는 경우) 당신이 생성 한 프로세스의 경우 일 수 있습니다 .
Craig Ringer

13
wait변수에 종료 코드를 반환하지 않습니다 $?. 종료 코드를 반환하고 $?최신 포 그라운드 프로그램의 종료 코드입니다.
MindlessRanger 2015-06-07

7
많은 사람들이 kill -0. 다음은 CraigRinger의 의견이 합법적이라는 것을 보여주는 동료 검토 참조 kill -0입니다. 실행중인 프로세스에 대해 0이 아닌 값을 ps -p반환 하지만 실행중인 프로세스에 대해서는 항상 0을 반환합니다 .
Trevor Boyd Smith

58

비슷한 필요가있을 때 이것이 내가 해결 한 방법입니다.

# 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 finish, will take max 14s
# as it waits in order of launch, not order of finishing
for p in $pids; do
        if wait $p; then
                echo "Process $p success"
        else
                echo "Process $p fail"
        fi
done

나는이 접근 방식을 좋아합니다.
Kemin Zhou

감사! 이것은 나에게 가장 간단한 접근 방식으로 보입니다.
누가 복음 데이비스

4
이 솔루션은 요구 사항 # 2 : 백그라운드 프로세스 당 모니터링 루프를 충족하지 않습니다. waits 스크립트는 (각) 프로세스가 끝날 때까지 대기합니다.
DKroot

간단하고 좋은 방법은 .. .. 아주 가끔이 솔루션을 찾고있다
산토 쉬 쿠마 아쥬 난

작동하지 않거나 원하는 작업을 수행하지 않습니다. 백그라운드 프로세스 종료 상태를 확인하지 않습니까?
conny nov.

10

백그라운드 하위 프로세스의 pid는 $! . 모든 자식 프로세스의 pid를 배열에 저장할 수 있습니다 (예 : PIDS []) .

wait [-n] [jobspec or pid …]

각 프로세스 ID pid 또는 작업 사양 jobspec에 지정된 하위 프로세스가 종료 될 때까지 기다렸다가 마지막으로 대기 한 명령의 종료 상태를 반환합니다. 작업 사양이 제공되면 작업의 모든 프로세스가 대기합니다. 인수가 제공되지 않으면 현재 활성화 된 모든 자식 프로세스가 대기하고 반환 상태는 0입니다. -n 옵션이 제공되면 작업이 종료 될 때까지 기다렸다가 종료 상태를 반환합니다. 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

나는 그것이 잘 돌아가는 것을 시도하고 증명했다. 내 설명을 코드로 읽을 수 있습니다.
Terry

"읽어주십시오 어떻게 좋은 답변을 작성하려면 어떻게해야합니까? "당신은 다음과 같은 정보를 찾을 볼 수있는 곳 : ... 당신의 대답에 제한 사항, 가정 또는 단순화를 언급하려고합니다. 간결함은 허용되지만 더 자세한 설명이 더 좋습니다. 따라서 대답은 허용되지만 문제와 솔루션에 대해 자세히 설명 할 수 있다면 찬성 투표를받을 가능성이 훨씬 더 높습니다. :-)
Noel Widmer

1
pid=$!; PIDS[$i]=${pid}; ((i+=1))PIDS+=($!)인덱싱이나 pid 자체에 별도의 변수를 사용할 필요없이 단순히 배열에 추가하는 방식 으로 더 간단하게 작성할 수 있습니다 . STATUS어레이 에도 동일한 내용이 적용됩니다 .
codeforester

1
@codeforester, 귀하의 제안에 감사드립니다. 초기 코드를 solution01로 수정했습니다. 더 간결 해 보입니다.
Terry

배열에 항목을 추가하는 다른 위치에도 동일한 사항이 적용됩니다.
codeforester

8

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

여기 제 예가 있습니다.

시작된 프로세스를라고 loop.sh합니다. -x인수로 또는 숫자를 받습니다 . For -x는 종료 코드 1로 종료됩니다. 숫자에 대해 num * 5 초를 기다립니다. 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 스크립트에서 프로세스 시작 실패를 참조하십시오.


1
Bash에 대해 이야기하고 있기 때문에 for 루프는 for i in ${!pids[@]};매개 변수 확장을 사용하여 작성 될 수 있습니다 .
PlasmaBinturong

8
#/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 $?

2
다음은 grep -v. 검색을 줄의 시작 부분으로 제한 할 수 있습니다. grep '^'$pid또한 ps p $pid -o pid=어쨌든 할 수 있습니다 . 또한, tail -f당신이 그것을 죽일 때까지 끝나지 않을 것이므로 (적어도 지적하지 않고) 이것을 데모하는 좋은 방법이라고 생각하지 않습니다. ps명령 의 출력을로 리디렉션 /dev/null하거나 반복 할 때마다 화면으로 이동할 수 있습니다. 당신의 exit원인은 wait건너 뜁니다-아마도 break. 그러나 while/ pswait중복되지 않습니까?
추후 공지가있을 때까지 일시 중지되었습니다.

5
왜 다들 잊어 버리 kill -0 $pid나요? 실제로 신호를 보내지 않고 외부 프로세스 대신 내장 된 셸을 사용하여 프로세스가 살아 있는지 확인합니다.
ephemient

3
소유 한 프로세스 만 죽일 수 있기 때문에 :bash: kill: (1) - Operation not permitted
errant.info

2
루프는 중복됩니다. 잠시만 기다려주세요. 더 적은 코드 => 더 적은 엣지 케이스.
Brais Gabin

@Brais Gabin 모니터링 루프는 질문의 요구 사항 # 2입니다
DKroot

5

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

#! / bin / sh

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

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

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

# 프로세스가 완료되면 리포터를 죽이는 트랩 설정
트랩 '$ echoer'0

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

멋진 템플릿, 공유해 주셔서 감사합니다! 나는 함정 대신 우리도 할 수 있다고 믿습니다 while kill -0 $pid 2> /dev/null; do X; done.이 메시지를 읽는 미래의 다른 누군가에게 유용하기를 바랍니다.)
punkbit

3

우리 팀은 25 분 동안 활동이 없으면 시간이 초과되는 원격 SSH 실행 스크립트에 대해 동일한 요구를 받았습니다. 다음은 모니터링 루프가 매초 백그라운드 프로세스를 확인하지만 비활성 시간 초과를 억제하기 위해 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}

2

위의 솔루션과 유사한 간단한 예입니다. 프로세스 출력을 모니터링 할 필요가 없습니다. 다음 예제에서는 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

tail을 사용하여 프로세스 출력을 따르고 프로세스가 완료되면 종료합니다.

$ 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

1

또 다른 해결책은 proc 파일 시스템 (ps / grep 콤보보다 안전함)을 통해 프로세스를 모니터링하는 것입니다. 프로세스를 시작할 때 / proc / $ pid에 해당 폴더가 있으므로 솔루션은

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

이제 원하는대로 $ exit_status 변수를 사용할 수 있습니다.


Bash에서 작동하지 않습니까? Syntax error: "else" unexpected (expecting "done")
benjaoming

1

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

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

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

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

추신 : btw, 나는 bash에서 생각을 코딩했습니다.


0

이것은 귀하의 질문을 넘어서 확장 될 수 있지만 프로세스가 실행되는 시간이 염려되는 경우 일정 시간 후에 실행중인 백그라운드 프로세스의 상태를 확인하는 데 관심이있을 수 있습니다. 를 사용하여 아직 실행중인 하위 PID를 확인하는 것은 간단 pgrep -P $$하지만 이미 만료 된 PID의 종료 상태를 확인하기 위해 다음 솔루션을 찾았습니다.

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

다음을 출력합니다.

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

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


0

내 해결책은 익명 파이프를 사용하여 상태를 모니터링 루프에 전달하는 것이 었습니다. 상태를 교환하는 데 사용되는 임시 파일이 없으므로 정리할 필요가 없습니다. 당신은 배경 작업의 수에 대해 불확실 있다면 휴식 상태가 될 수 있습니다 [ -z "$(jobs -p)" ].

#!/bin/bash

exec 3<> <(:)

{ sleep 15 ; echo "sleep/exit $?" >&3 ; } &

while read -u 3 -t 1 -r STAT CODE || STAT="timeout" ; do
    echo "stat: ${STAT}; code: ${CODE}"
    if [ "${STAT}" = "sleep/exit" ] ; then
        break
    fi
done
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.