2011-03-01 73 views
116

我有一個bash腳本,用於啓動一個子進程,它不時崩潰(實際上是掛起),沒有明顯的原因(關閉源代碼,所以我沒有太多的辦法可以做到)。因此,我希望能夠在給定的時間內啓動此過程,如果它在給定的時間之後沒有成功返回,就會殺死它。如何在Bash中的給定超時後殺死子進程?

有沒有簡單健壯方式來實現,使用bash?

P.S .:告訴我這個問題是否更適合serverfault或超級用戶。

+0

相關:http://stackoverflow.com/q/601543/132382 – pilcrow 2014-11-08 01:05:20

回答

179

(曾經出現在: BASH FAQ entry #68: "How do I run a command, and have it abort (timeout) after N seconds?"

如果你不介意下載東西,用timeoutsudo apt-get install timeout),並使用它像:

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) 
+5

在Ignacio的回覆中,如果有人想知道我做了什麼:'cmdpid = $ BASHPID'將不會接收* calling * shell的pid,但由()開始的(第一個)子shell。 '(睡眠)...東西在第一個子shell中調用第二個子shell在後臺等待10秒,並終止第一個子shell,在啓動殺手子shell進程後,繼續執行其工作負載... – jamadagni 2014-06-08 01:12:50

+13

'timeout '是GNU coreutils的一部分,所以應該已經安裝在所有GNU系統中。 – Sameer 2015-02-13 22:34:26

+1

@Sameer:僅限於版本8. – 2015-02-13 22:37:39

1

假設你有(或可以很容易地)製作一個用於跟蹤孩子pid的pid文件,那麼你可以創建一個腳本來檢查pid文件的modtime並根據需要殺死/重新生成該進程。然後,只需將腳本放在crontab中即可在大約需要的時間運行。

讓我知道你是否需要更多的細節。如果這聽起來並不像它會滿足您的需求,何談upstart?

21
# 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'。 – 2011-03-02 02:31:02

+0

的確,我正在尋求一種快速修復方法,只是假設他希望這個過程立即死亡,因爲他說它崩潰了 – Dan 2011-03-02 15:27:56

+6

這實際上是一個非常糟糕的解決方案。如果'dosmth'在2秒鐘內終止,另一個進程將採用舊的pid,並且殺死新的pid? – 2017-01-03 10:26:07

1

一種方法是在子shell中運行程序,並使用read命令通過命名管道與子shell進行通信。通過這種方式,您可以檢查正在運行的進程的退出狀態,並通過管道回傳。

下面是在3秒後超時yes命令的示例。它使用pgrep獲得進程的PID(可能只適用於Linux)。使用管道也存在一些問題,打開管道進行讀取的過程將掛起,直到它也打開寫入,反之亦然。所以爲了防止read命令掛起,我已經「楔住」打開管道以用背景子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 
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 

由於這是一個循環我收錄了「睡0.2」,以保持涼爽的CPU。 ;-)

(BTW:平安是一個壞榜樣,無論如何,你只是會使用內置的「-t」(超時)選項)

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)幾乎相同,只是如果超時外殼已經結束,它不會被終止。

這個程序結束後,仍然會有一些運行的「睡眠」過程,但它們應該是無害的。

這可能比我的其他答案一個更好的解決方案,因爲它不使用非便攜式外殼功能​​並且不使用pgrep

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 
    "[email protected]" & 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 

:對我來說,後者給出了一個錯誤。