56

一定時間後にコマンドを自動的に強制終了したいと思います。私は次のようなインターフェースを念頭に置いています:

% constrain 300 ./foo args

これは、「./foo」を「args」で実行しますが、5 分後にまだ実行されている場合は自動的に強制終了します。

メモリを使いすぎる場合にプロセスを自動強制終了するなど、他の制約にアイデアを一般化すると役立つ場合があります。

それを行う既存のツールはありますか、またはそのようなことを書いた人はいますか?

追加: Jonathan のソリューションはまさに私が考えていたものであり、Linux では魅力的に機能しますが、Mac OSX では機能しません。正常にコンパイルできる SIGRTMIN を削除しましたが、シグナルが子プロセスに送信されません。Macでこれを機能させる方法を知っている人はいますか?

[追加: Mac などで動作するアップデートが Jonathan から入手できることに注意してください。]

4

15 に答える 15

48

GNU Coreutils にはtimeoutコマンドが含まれており、多くのシステムにデフォルトでインストールされています。

https://www.gnu.org/software/coreutils/manual/html_node/timeout-invocation.html

1 分間監視free -mするには、TERM シグナルを送信して強制終了します。

timeout 1m watch free -m
于 2011-08-24T02:59:17.653 に答える
30

このパーティーにかなり遅れて到着しましたが、お気に入りのトリックが回答にリストされていません。

*NIX では、 analarm(2)は an に継承execve(2)され、SIGALRM はデフォルトで致命的です。したがって、多くの場合、次のことが簡単にできます。

$ doalarm () { perl -e 'alarm shift; exec @ARGV' "$@"; } # define a helper function

$ doalarm 300 ./foo.sh args

または、簡単な C ラッパーをインストールしてそれを行います。

利点PID が 1 つだけで、メカニズムが単純です。./foo.shたとえば、 「あまりにも早く」終了し、その PID が再利用された場合、間違ったプロセスを強制終了することはありません。複数のシェル サブプロセスが協調して動作する必要はありません。これは正しく実行できますが、競合が発生しやすくなります。

短所時間に制約のあるプロセスは、継承されたアラームをクリアする可能性が高いため、目覚まし時計 (例: 、 、alarm(2))を操作できません。明らかに、SIGALRM をブロックしたり無視したりすることはできませんが、他のアプローチの SIGINT や SIGTERM などについても同じことが言えます。ualarm(2)setitimer(2)

一部の (非常に古いと思います) システムsleep(2)は の観点から実装されalarm(2)ており、今日でも一部のプログラマーalarm(2)は I/O やその他の操作のための粗雑な内部タイムアウト メカニズムとして使用しています。ただし、私の経験では、この手法は、時間制限を設定する大部分のプロセスに適用できます。

于 2010-07-11T14:49:51.117 に答える
13

私はそれを行うというプログラムを持っていますtimeout.Cで書かれ、もともとは1989年に書かれていましたが、それ以来定期的に更新されています.


更新: SIGRTMIN が定義されていないため、このコードは MacOS X でのコンパイルに失敗し、MacOS X で実行するとタイムアウトに失敗します。必要な動作。これらの問題の両方に対処する新しいバージョンの `timeout.c` があります (`signal()` の代わりに `sigaction()` を使用)。以前と同様に、ソース コードとマニュアル ページを含む 10K の gzip 圧縮された tar ファイルについては、私に連絡してください (私のプロフィールを参照してください)。
/*
@(#)File:           $RCSfile: timeout.c,v $
@(#)Version:        $Revision: 4.6 $
@(#)Last changed:   $Date: 2007/03/01 22:23:02 $
@(#)Purpose:        Run command with timeout monitor
@(#)Author:         J Leffler
@(#)Copyright:      (C) JLSS 1989,1997,2003,2005-07
*/

#define _POSIX_SOURCE       /* Enable kill() in <unistd.h> on Solaris 7 */
#define _XOPEN_SOURCE 500

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "stderr.h"

#define CHILD       0
#define FORKFAIL    -1

static const char usestr[] = "[-vV] -t time [-s signal] cmd [arg ...]";

#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
const char jlss_id_timeout_c[] = "@(#)$Id: timeout.c,v 4.6 2007/03/01 22:23:02 jleffler Exp $";
#endif /* lint */

static void catcher(int signum)
{
    return;
}

int main(int argc, char **argv)
{
    pid_t   pid;
    int     tm_out;
    int     kill_signal;
    pid_t   corpse;
    int     status;
    int     opt;
    int     vflag = 0;

    err_setarg0(argv[0]);

    opterr = 0;
    tm_out = 0;
    kill_signal = SIGTERM;
    while ((opt = getopt(argc, argv, "vVt:s:")) != -1)
    {
        switch(opt)
        {
        case 'V':
            err_version("TIMEOUT", &"@(#)$Revision: 4.6 $ ($Date: 2007/03/01 22:23:02 $)"[4]);
            break;
        case 's':
            kill_signal = atoi(optarg);
            if (kill_signal <= 0 || kill_signal >= SIGRTMIN)
                err_error("signal number must be between 1 and %d\n", SIGRTMIN - 1);
            break;
        case 't':
            tm_out = atoi(optarg);
            if (tm_out <= 0)
                err_error("time must be greater than zero (%s)\n", optarg);
            break;
        case 'v':
            vflag = 1;
            break;
        default:
            err_usage(usestr);
            break;
        }
    }

    if (optind >= argc || tm_out == 0)
        err_usage(usestr);

    if ((pid = fork()) == FORKFAIL)
        err_syserr("failed to fork\n");
    else if (pid == CHILD)
    {
        execvp(argv[optind], &argv[optind]);
        err_syserr("failed to exec command %s\n", argv[optind]);
    }

    /* Must be parent -- wait for child to die */
    if (vflag)
        err_remark("time %d, signal %d, child PID %u\n", tm_out, kill_signal, (unsigned)pid);
    signal(SIGALRM, catcher);
    alarm((unsigned int)tm_out);
    while ((corpse = wait(&status)) != pid && errno != ECHILD)
    {
        if (errno == EINTR)
        {
            /* Timed out -- kill child */
            if (vflag)
                err_remark("timed out - send signal %d to process %d\n", (int)kill_signal, (int)pid);
            if (kill(pid, kill_signal) != 0)
                err_syserr("sending signal %d to PID %d - ", kill_signal, pid);
            corpse = wait(&status);
            break;
        }
    }

    alarm(0);
    if (vflag)
    {
        if (corpse == (pid_t) -1)
            err_syserr("no valid PID from waiting - ");
        else
            err_remark("child PID %u status 0x%04X\n", (unsigned)corpse, (unsigned)status);
    }

    if (corpse != pid)
        status = 2; /* I don't know what happened! */
    else if (WIFEXITED(status))
        status = WEXITSTATUS(status);
    else if (WIFSIGNALED(status))
        status = WTERMSIG(status);
    else
        status = 2; /* I don't know what happened! */

    return(status);
}

「stderr.h」と「stderr.c」の「公式」コードが必要な場合は、私に連絡してください (私のプロフィールを参照してください)。

于 2009-03-02T07:59:03.487 に答える
4

キック用のPerlワンライナー:

perl -e '$s = shift; $SIG{ALRM} = sub { print STDERR "Timeout!\n"; kill INT => $p }; exec(@ARGV) unless $p = fork; alarm $s; waitpid $p, 0' 10 yes foo

これは 'foo' を 10 秒間出力してからタイムアウトします。「10」を任意の秒数に置き換え、「yes foo」を任意のコマンドに置き換えます。

于 2009-03-11T20:09:52.907 に答える
4

perl ワンライナーの私のバリエーションは、 fork() と wait() をいじることなく、間違ったプロセスを強制終了するリスクなしに、終了ステータスを提供します。

#!/bin/sh
# Usage: timelimit.sh secs cmd [ arg ... ]
exec perl -MPOSIX -e '$SIG{ALRM} = sub { print "timeout: @ARGV\n"; kill(SIGTERM, -$$); }; alarm shift; $exit = system @ARGV; exit(WIFEXITED($exit) ? WEXITSTATUS($exit) : WTERMSIG($exit));' "$@"

基本的に、fork() と wait() は system() 内に隠されています。SIGALRM は親プロセスに配信され、親プロセスは SIGTERM をプロセス グループ全体 (-$$) に送信することで、それ自体とその子を強制終了します。子が終了し、kill() が発生する前に子の pid が再利用されるというまれなイベントでは、古い子の pid を持つ新しいプロセスが親の perl プロセスと同じプロセス グループにないため、間違ったプロセスが強制終了されることはありません。 .

追加の利点として、スクリプトはおそらく正しい終了ステータスで終了します。

于 2010-10-06T14:22:07.380 に答える
4

Mac で動作するようにソースからコンパイルした場合の Ubuntu/Debian からのタイムアウト コマンド。ダーウィン

10.4.*

http://packages.ubuntu.com/lucid/timeout

于 2010-09-22T11:40:02.520 に答える
3
#!/bin/sh
( some_slow_task ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher

ウォッチャーは、指定されたタイムアウト後に遅いタスクを強制終了します。スクリプトは遅いタスクを待機し、ウォッチャーを終了します。

例:

  • 遅いタスクが 2 秒以上実行され、終了しました

遅いタスクが中断されました

( sleep 20 ) & pid=$!
( sleep 2 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "Slow task finished"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "Slow task interrupted"
fi
  • この遅いタスクは、指定されたタイムアウト前に終了しました

遅いタスクが完了しました

( sleep 2 ) & pid=$!
( sleep 20 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "Slow task finished"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "Slow task interrupted"
fi
于 2012-06-15T18:09:40.933 に答える
2

純粋なbash:


#!/bin/bash

if [[ $# < 2 ]]; then
  echo "Usage: $0 timeout cmd [options]"
  exit 1
fi

TIMEOUT="$1"
shift

BOSSPID=$$

(
  sleep $TIMEOUT
  kill -9 -$BOSSPID
)&
TIMERPID=$!

trap "kill -9 $TIMERPID" EXIT

eval "$@"
于 2010-03-16T15:01:19.660 に答える
2

次のようなものを試してください:

# This function is called with a timeout (in seconds) and a pid.
# After the timeout expires, if the process still exists, it attempts
# to kill it.
function timeout() {
    sleep $1
    # kill -0 tests whether the process exists
    if kill -0 $2 > /dev/null 2>&1 ; then
        echo "killing process $2"
        kill $2 > /dev/null 2>&1
    else
        echo "process $2 already completed"
    fi
}

<your command> &
cpid=$!
timeout 3 $cpid
wait $cpid > /dev/null 2>&
exit $?

プロセスのpidがタイムアウト内に再利用されると、間違ったプロセスが強制終了される可能性があるという欠点があります。これはほとんどあり得ませんが、1秒あたり20000以上のプロセスを開始している可能性があります。これは修正される可能性があります。

于 2010-02-12T15:02:11.123 に答える
2

debian リポジトリで利用可能なパッケージである「timelimit」を使用します。

http://devel.ringlet.net/sysutils/timelimit/

于 2010-08-15T09:47:08.333 に答える
2

期待ツールを使用するのはどうですか?

## run a command, aborting if timeout exceeded, e.g. timed-run 20 CMD ARGS ...
timed-run() {
  # timeout in seconds
  local tmout="$1"
  shift
  env CMD_TIMEOUT="$tmout" expect -f - "$@" <<"EOF"
# expect script follows
eval spawn -noecho $argv
set timeout $env(CMD_TIMEOUT)
expect {
   timeout {
      send_error "error: operation timed out\n"
      exit 1
   }
   eof
}
EOF
}
于 2010-02-18T16:56:10.087 に答える
1

perl のワンライナーを少し変更すると、終了ステータスが正しくなります。

perl -e '$s = shift; $SIG{ALRM} = sub { print STDERR "Timeout!\n"; kill INT => $p; exit 77 }; exec(@ARGV) unless $p = fork; alarm $s; waitpid $p, 0; exit ($? >> 8)' 10 yes foo

基本的に、 exit ($? >> 8) はサブプロセスの終了ステータスを転送します。タイムアウトの終了ステータスで 77 を選択しました。

于 2009-10-11T17:45:37.160 に答える
1

これを行うために「at」で特定の時間を設定する方法はありませんか?

$ at 05:00 PM kill -9 $pid

はるかに単純に思えます。

pid番号がどうなるかわからない場合は、ps auxとgrepを使用してそれを読み取るスクリプトを作成する方法があると思いますが、それを実装する方法がわかりません。

$   | grep someprogram
tony     11585  0.0  0.0   3116   720 pts/1    S+   11:39   0:00 grep someprogram
tony     22532  0.0  0.9  27344 14136 ?        S    Aug25   1:23 someprogram

スクリプトは pid を読み取り、それに変数を割り当てる必要があります。私はあまり熟練していませんが、これは実行可能だと思います。

于 2010-08-31T15:40:44.330 に答える