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수신 될 때 백그라운드 프로세스를 정리합니다 . 그것은 사용 $BASHPID및 exec에 사용되는 트릭 최고 응답 (이 경우 프로세스의 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.