如何在Bash中给定超时后杀死子进程?

时间:2011-03-01 22:27:38

标签: linux bash unix

我有一个bash脚本,它会启动一个不时崩溃(实际上是挂起)的子进程,并且没有明显的原因(封闭源代码,因此我无能为力)。因此,我希望能够在给定的时间内启动此过程,如果在给定的时间后没有成功返回,则将其终止。

是否有简单强大方式来实现使用bash?

P.S。:告诉我这个问题是否更适合服务器故障或超级用户。

9 个答案:

答案 0 :(得分:232)

(如下所示: BASH FAQ entry #68: "How do I run a command, and have it abort (timeout) after N seconds?"

如果您不介意下载某些内容,请使用timeoutsudo 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 )

答案 1 :(得分:26)

# 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=$?

答案 2 :(得分:10)

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

答案 3 :(得分:3)

我也有这个问题,发现还有两件非常有用的东西:

  1. bash中的SECONDS变量。
  2. 命令“pgrep”。
  3. 所以我在命令行(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
    

    由于这是一个循环,我包含了“睡眠0.2”以保持CPU冷却。 ; - )

    (顺便说一句:ping是一个不好的例子,你只需使用内置的“-t”(超时)选项。)

答案 4 :(得分:1)

假设您已经(或可以轻松制作)用于跟踪孩子的pid的pid文件,您可以创建一个脚本来检查pid文件的modtime并根据需要杀死/重新生成该过程。然后将脚本放在crontab中,大约在你需要的时间内运行。

如果您需要更多详细信息,请与我们联系。如果这听起来不适合您的需求,那么upstart?

答案 5 :(得分:1)

一种方法是在子shell中运行程序,并使用read命令通过命名管道与子shell进行通信。这样,您可以检查正在运行的进程的退出状态,并通过管道将其传回。

这是一个在3秒后超时yes命令的示例。它使用pgrep获取进程的PID(可能仅适用于Linux)。使用管道也存在一些问题,因为打开管道进行读取的过程将一直挂起,直到它也被打开以进行写入,反之亦然。因此,为了防止read命令挂起,我已经&#34;楔入&#34;用后台子shell打开管道进行读取。 (防止冻结打开管道读写的另一种方法,即read -t 5 <>finished.pipe - 但是,除了Linux之外,这也可能不起作用。)

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

答案 6 :(得分: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
  )
}

run_with_timeout 3 sleep 10000一样使用sleep 10000,但会在3秒后结束。

这就像其他使用后台超时进程在延迟后终止子进程的答案一样。我认为这与Dan的扩展答案(https://stackoverflow.com/a/5161274/1351983)几乎相同,但是如果超时shell已经结束,则不会被终止。

这个节目结束后,仍然会有一些挥之不去的睡眠&#34;流程运行,但它们应该是无害的。

这可能是比我的其他答案更好的解决方案,因为它不使用非便携式shell功能read -t而且不使用pgrep

答案 7 :(得分:0)

这是我在这里提交的第三个答案。这个处理信号中断并在收到SIGINT时清理后台进程。它使用top answer中使用的$BASHPIDexec技巧来获取进程的PID(在这种情况下$$调用sh。它使用FIFO与负责杀死和清理的子shell进行通信。 (这就像我second answer中的管道一样,但是有一个命名管道意味着信号处理程序也可以写入它。)

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 2run_with_timeout 0 sleep 0。对我来说,后者给出了一个错误:

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

因为它试图杀死一个已经退出的进程。

答案 8 :(得分:0)

#Kill command after 10 seconds
timeout 10 command

#If you don't have timeout installed, this is almost the same:
sh -c '(sleep 10; kill "$$") & command'

#The same as above, with muted duplicate messages:
sh -c '(sleep 10; kill "$$" 2>/dev/null) & command'