web-dev-qa-db-ja.com

一定時間後にコマンドを自動終了するコマンドラインコマンド

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

% constrain 300 ./foo args

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

あまりにも多くのメモリを使用するプロセスの自動キルなど、他の制約にアイデアを一般化することは有用かもしれません。

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

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

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

52
dreeves

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

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

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

$ doalarm 300 ./foo.sh args
_

または トリビアルCラッパー をインストールしてください。

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

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

一部の(非常に古いと思います)システムはsleep(2)alarm(2)として実装しており、一部のプログラマーはalarm(2)を粗い内部タイムアウトメカニズムとして使用していますI/Oおよびその他の操作。しかし、私の経験では、この手法は時間制限をかけたいプロセスの大部分に適用できます。

27
pilcrow

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

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

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

timeout 1m watch free -m
45
Roger Dahl

多分私は質問を理解していませんが、これは少なくともbashでは直接実行可能に聞こえます:

( /path/to/slow command with options ) & sleep 5 ; kill $!

これにより、括弧内の最初のコマンドが5秒間実行され、その後それが強制終了されます。操作全体が同期的に実行されます。つまり、遅いコマンドを待っているビジー状態の間はシェルを使用できません。それがあなたが望んだものではないなら、別の&を追加することが可能であるべきです。

$!変数は、最後に開始されたサブシェルのプロセスIDを含むBash組み込みです。括弧内に&を入れないことが重要です。そのようにすると、プロセスIDが失われます。

30
unwind

サブプロセスで使用可能な実行時間を制限するために使用できるulimitもあります。

ulimit -t 10

プロセスをCPU時間の10秒に制限します。

現在のプロセスではなく、実際にそれを使用して新しいプロセスを制限するには、ラッパースクリプトを使用できます。

#! /usr/bin/env python

import os
os.system("ulimit -t 10; other-command-here")

other-commandは任意のツールです。実行時間を30秒に制限しながら、Java、Python、C、Schemeバージョンのさまざまな並べ替えアルゴリズムを実行し、それらにかかった時間をログに記録していました。 Cocoa-Pythonアプリケーションは、引数を含むさまざまなコマンドラインを生成し、時刻をCSVファイルに照合しましたが、実際には、上記のコマンドに加えて綿密なものでした。

11

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プロセスの同じプロセスグループにないため、これは間違ったプロセスを強制終了しません。

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

4
bjaspan

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

10.4。*

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

4
sheki

Perl one liner、キックだけのために:

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」を任意のコマンドに置き換えます。

4
vasi

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

# 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がタイムアウト内で再利用されると、誤ったプロセスが強制終了される可能性があるという欠点があります。これは非常にまれですが、毎秒20000以上のプロセスを開始している可能性があります。これは修正される可能性があります。

2
fadedbee
#!/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
2
Dmitry

私は「timelimit」を使用しています。これは、debianリポジトリーで入手可能なパッケージです。

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

2
maxy

Expectツールの使用についてはどうですか?

## 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
}
1
user276283

これを行うために「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を読み取って変数に割り当てる必要があります。私はあまり熟練していませんが、これは実行可能だと思います。

1
tonybaldwin

純粋な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 "$@"
1
yrn1

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を選択しました。

1
user143785