Bash에서 지정된 시간 초과 후 자식 프로세스를 종료하는 방법은 무엇입니까?


178

나는 때때로 충돌하고 (실제로 멈추는) 자식 프로세스를 시작하는 명백한 이유없이 bash 스크립트를 가지고 있습니다 (닫힌 소스, 그래서 그것에 대해 할 수있는 일은 많지 않습니다). 결과적으로 주어진 시간 동안이 프로세스를 시작하고 주어진 시간 후에 성공적으로 반환되지 않으면 프로세스를 종료하고 싶습니다.

거기에 간단 하고 강력한 것을 사용하여 떠들썩한 파티를 달성하는 방법은?

추신 :이 질문이 serverfault 또는 수퍼 유저에게 더 적합한 지 알려주십시오.



여기에 매우 완전한 답변 : stackoverflow.com/a/58873049/2635443
Orsiris de Jong

답변:


260

( BASH FAQ 항목 # 68 : "명령을 실행하고 N 초 후에 중단 (시간 초과)하는 방법은 무엇입니까?" )

다운로드가 마음에 들지 않으면 timeout( sudo apt-get install timeout)를 사용하고 다음과 같이 사용하십시오 (대부분의 시스템에는 이미 설치되어 있습니다 sudo apt-get install coreutils)

timeout 10 ping www.goooooogle.com

무언가를 다운로드하지 않으려면 내부에서 시간 초과가 수행하는 작업을 수행하십시오.

( cmdpid=$BASHPID; (sleep 10; kill $cmdpid) & exec ping www.goooooogle.com )

긴 bash 코드에 대해 시간 초과를 수행하려는 경우 다음과 같이 두 번째 옵션을 사용하십시오.

( cmdpid=$BASHPID; 
    (sleep 10; kill $cmdpid) \
   & while ! ping -w 1 www.goooooogle.com 
     do 
         echo crap; 
     done )

8
다른 사람이 내가 한 일을 궁금해하는 경우를 대비 한 Re Ignacio의 답변 : 호출cmdpid=$BASHPID의 pid를 취하지 않고 시작한 (첫 번째) 서브 쉘을 가져옵니다 . ... 것은 ... 백그라운드에서 10 초를 기다린 후 킬러 서브 쉘 프로세스를 실행 한 후, 작업 부하를 실행하기 위해 진행 첫 번째 서브 쉘을 죽일 첫 번째 서브 쉘 내에서 두 번째 서브 쉘을 호출()(sleep
jamadagni

17
timeoutGNU coreutils의 일부이므로 모든 GNU 시스템에 이미 설치되어 있어야합니다.
Sameer

1
@Sameer : 버전 8 기준.
Ignacio Vazquez-Abrams

3
나는 그것을 100 % 확신하지 못하지만, 내가 아는 한 (그리고 내 맨 페이지가 나에게 말한 것을 아는 한) timeout이제는 coreutils의 일부입니다.
benaryorg

5
이 명령은 '초기'가 아닙니다. 시간 초과시 항상 프로세스를 종료하지만 시간 초과되지 않은 상황은 처리하지 않습니다.
hawkeye

28
# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) &

또는 종료 코드를 얻으려면 :

# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) & waiter=$!
# wait on our worker process and return the exitcode
exitcode=$(wait $pid && echo $?)
# kill the waiter subshell, if it still runs
kill -9 $waiter 2>/dev/null
# 0 if we killed the waiter, cause that means the process finished before the waiter
finished_gracefully=$?

8
kill -9프로세스가 먼저 처리 할 수 ​​있다는 신호를 시도하기 전에 사용해서는 안됩니다 .
추후 공지가있을 때까지 일시 중지되었습니다.

사실, 나는 빠른 수정을하려고했으나 그가 충돌했다고 말했기 때문에 프로세스가 즉시 죽기를 원한다고 가정했다
Dan

8
그것은 실제로 매우 나쁜 해결책입니다. 만약에 dosmth종료 이초에서, 다른 프로세스는 기존의 PID를합니다, 당신은 새를 죽일?
순간 이동 염소

PID 재활용은 한계에 도달하고 감싸는 방식으로 작동합니다. 시스템이 완전하게 배선되지 않으면 다른 프로세스가 나머지 8 초 내에 PID를 재사용 할 가능성이 거의 없습니다.
kittydoor

13
sleep 999&
t=$!
sleep 10
kill $t

과도한 대기가 발생합니다. 실제 명령 ( sleep 999여기)이 종종 부과 된 수면 ( sleep 10) 보다 빨리 완료되면 어떻게됩니까? 최대 1 분, 5 분의 기회를 주려면 어떻게해야합니까? 내 스크립트에 이런 경우가 많이 있다면 어떻게합니까? :)
it3xl

3

나는 또한이 질문을했고 두 가지 더 유용한 것을 발견했습니다.

  1. bash의 SECONDS 변수
  2. "pgrep"명령

그래서 나는 커맨드 라인 (OSX 10.9)에서 이와 같은 것을 사용합니다 :

ping www.goooooogle.com & PING_PID=$(pgrep 'ping'); SECONDS=0; while pgrep -q 'ping'; do sleep 0.2; if [ $SECONDS = 10 ]; then kill $PING_PID; fi; done

이것이 루프이므로 CPU를 시원하게 유지하기 위해 "절전 0.2"를 포함했습니다. ;-)

(BTW : 핑은 어쨌든 나쁜 예입니다. 내장 "-t"(시간 초과) 옵션을 사용하면됩니다.)


1

자식의 pid를 추적하기 위해 pid 파일을 가지고 있거나 쉽게 만들 수 있다고 가정하면 pid 파일의 모드 시간을 확인하고 필요에 따라 프로세스를 종료 / 다시 생성하는 스크립트를 만들 수 있습니다. 그런 다음 스크립트를 crontab에 넣어 대략 필요한 시간에 실행하십시오.

자세한 내용이 필요하면 알려주십시오. 그것이 귀하의 필요에 맞지 않는 것처럼 들리면, 시작은 어떻습니까?


1

한 가지 방법은 서브 쉘에서 프로그램을 실행하고 read명령 을 사용하여 명명 된 파이프를 통해 서브 쉘과 통신하는 것 입니다. 이렇게하면 실행중인 프로세스의 종료 상태를 확인하고 파이프를 통해 다시 통신 할 수 있습니다.

다음은 yes3 초 후에 명령 을 시간 초과하는 예입니다 . 사용하여 프로세스의 PID를 얻습니다 pgrep(Linux에서만 작동 할 수 있음). 파이프를 사용하는 데에도 문제가 있습니다. 파이프를 읽기 위해 열면 프로세스가 쓰기를 위해 열릴 때까지 멈추거나 그 반대도 마찬가지입니다. 따라서 read명령이 중단 되는 것을 방지하기 위해 백그라운드 서브 쉘로 읽을 파이프를 "쐐기"했습니다. (동결 파이프 읽기-쓰기를 여는 동결을 방지하는 다른 방법, 즉 read -t 5 <>finished.pipeLinux를 제외하고는 작동하지 않을 수도 있습니다.)

rm -f finished.pipe
mkfifo finished.pipe

{ yes >/dev/null; echo finished >finished.pipe ; } &
SUBSHELL=$!

# Get command PID
while : ; do
    PID=$( pgrep -P $SUBSHELL yes )
    test "$PID" = "" || break
    sleep 1
done

# Open pipe for writing
{ exec 4>finished.pipe ; while : ; do sleep 1000; done } &  

read -t 3 FINISHED <finished.pipe

if [ "$FINISHED" = finished ] ; then
  echo 'Subprocess finished'
else
  echo 'Subprocess timed out'
  kill $PID
fi

rm finished.pipe

0

다음은 프로세스가 이미 종료 된 후 프로세스 종료를 피하려고 시도하는 것입니다. 동일한 프로세스 ID로 다른 프로세스를 종료 할 가능성이 줄어 듭니다 (이러한 종류의 오류를 완전히 피하는 것은 불가능할 수도 있음).

run_with_timeout ()
{
  t=$1
  shift

  echo "running \"$*\" with timeout $t"

  (
  # first, run process in background
  (exec sh -c "$*") &
  pid=$!
  echo $pid

  # the timeout shell
  (sleep $t ; echo timeout) &
  waiter=$!
  echo $waiter

  # finally, allow process to end naturally
  wait $pid
  echo $?
  ) \
  | (read pid
     read waiter

     if test $waiter != timeout ; then
       read status
     else
       status=timeout
     fi

     # if we timed out, kill the process
     if test $status = timeout ; then
       kill $pid
       exit 99
     else
       # if the program exited normally, kill the waiting shell
       kill $waiter
       exit $status
     fi
  )
}

like를 사용하면 run_with_timeout 3 sleep 10000실행 sleep 10000되지만 3 초 후에 종료됩니다.

이것은 지연 후 자식 프로세스를 종료하기 위해 백그라운드 시간 초과 프로세스를 사용하는 다른 답변과 같습니다. 타임 아웃 쉘이 이미 종료 된 경우 종료되지 않는다는 점을 제외하고 이것은 Dan의 확장 답변 ( https : //.com/a/5161274/1351983 ) 과 거의 동일하다고 생각합니다 .

이 프로그램이 종료 된 후에도 여전히 몇 개의 느린 "절전"프로세스가 실행되지만 무해해야합니다.

이것은 이식 할 수없는 쉘 기능을 사용하지 않기 때문에 다른 대답보다 더 나은 해결책 일 수 있습니다 read -t 및 사용하지 않습니다 pgrep.


차이 무엇 (exec sh -c "$*") &sh -c "$*" &? 특히 왜 후자를 사용하지 않는가?
저스틴 C

0

여기 내가 제출 한 세 번째 답변이 있습니다. 이것은 신호 인터럽트를 처리하고 SIGINT수신 될 때 백그라운드 프로세스를 정리합니다 . 그것은 사용 $BASHPIDexec에 사용되는 트릭 최고 응답 (이 경우 프로세스의 PID를 얻기 위해 $$A의 sh호출). FIFO를 사용하여 종료 및 정리를 담당하는 서브 쉘과 통신합니다. (이것은 두 번째 대답 의 파이프와 같지만 파이프 이름이 지정되면 신호 처리기가 파이프에 쓸 수도 있습니다.)

run_with_timeout ()
{
  t=$1 ; shift

  trap cleanup 2

  F=$$.fifo ; rm -f $F ; mkfifo $F

  # first, run main process in background
  "$@" & pid=$!

  # sleeper process to time out
  ( sh -c "echo \$\$ >$F ; exec sleep $t" ; echo timeout >$F ) &
  read sleeper <$F

  # control shell. read from fifo.
  # final input is "finished".  after that
  # we clean up.  we can get a timeout or a
  # signal first.
  ( exec 0<$F
    while : ; do
      read input
      case $input in
        finished)
          test $sleeper != 0 && kill $sleeper
          rm -f $F
          exit 0
          ;;
        timeout)
          test $pid != 0 && kill $pid
          sleeper=0
          ;;
        signal)
          test $pid != 0 && kill $pid
          ;;
      esac
    done
  ) &

  # wait for process to end
  wait $pid
  status=$?
  echo finished >$F
  return $status
}

cleanup ()
{
  echo signal >$$.fifo
}

가능한 한 경쟁 조건을 피하려고 노력했습니다. 그러나 제거 할 수없는 오류의 원인은 프로세스가 시간 초과와 거의 같은 시간에 끝날 때입니다. 예를 들어 run_with_timeout 2 sleep 2또는 run_with_timeout 0 sleep 0. 나에게 후자는 오류를 준다.

timeout.sh: line 250: kill: (23248) - No such process

이미 자체 종료 된 프로세스를 종료하려고합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.