一定時間後に自動的にコマンドを強制終了したいのですが。私はこのようなインターフェースを念頭に置いています:
% constrain 300 ./foo args
これは、「./ foo」を「args」で実行しますが、5分後にまだ実行されている場合は自動的に強制終了します。
あまりにも多くのメモリを使用するプロセスの自動キルなど、他の制約にアイデアを一般化することは有用かもしれません。
それを行う既存のツールはありますか、または誰かがそのようなことを書いていますか?
追加:ジョナサンのソリューションはまさに私が考えていたものであり、Linuxの魅力のように機能しますが、Mac OSXで機能させることはできません。うまくコンパイルできるSIGRTMINを削除しましたが、シグナルが子プロセスに送信されません。これをMacで動作させる方法を知っている人はいますか?
[追加:Macなどで動作するアップデートがJonathanから入手できることに注意してください。]
私はこのパーティーにかなり遅れて到着しましたが、私のお気に入りのトリックが回答にリストされていません。
* 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およびその他の操作。しかし、私の経験では、この手法は時間制限をかけたいプロセスの大部分に適用できます。
GNU Coreutilsには、timeoutコマンドが含まれており、デフォルトで多くのシステムにインストールされています。
https://www.gnu.org/software/coreutils/manual/html_node/timeout-invocation.html
視聴するにはfree -m
1分間、TERMシグナルを送信して終了します。
timeout 1m watch free -m
多分私は質問を理解していませんが、これは少なくともbashでは直接実行可能に聞こえます:
( /path/to/slow command with options ) & sleep 5 ; kill $!
これにより、括弧内の最初のコマンドが5秒間実行され、その後それが強制終了されます。操作全体が同期的に実行されます。つまり、遅いコマンドを待っているビジー状態の間はシェルを使用できません。それがあなたが望んだものではないなら、別の&を追加することが可能であるべきです。
$!
変数は、最後に開始されたサブシェルのプロセスIDを含むBash組み込みです。括弧内に&を入れないことが重要です。そのようにすると、プロセスIDが失われます。
サブプロセスで使用可能な実行時間を制限するために使用できる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ファイルに照合しましたが、実際には、上記のコマンドに加えて綿密なものでした。
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プロセスの同じプロセスグループにないため、これは間違ったプロセスを強制終了しません。
追加の利点として、スクリプトはおそらくが正しい終了ステータスで終了します。
Macで動作するようにソースからコンパイルされたときのUbuntu/Debianからのタイムアウトコマンド。ダーウィン
10.4。*
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」を任意のコマンドに置き換えます。
次のようなものを試してください:
# 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以上のプロセスを開始している可能性があります。これは修正される可能性があります。
#!/bin/sh
( some_slow_task ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher
ウォッチャーは、指定されたタイムアウト後に遅いタスクを強制終了します。スクリプトは遅いタスクを待ち、ウォッチャーを終了します。
遅いタスクが中断されました
( 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
私は「timelimit」を使用しています。これは、debianリポジトリーで入手可能なパッケージです。
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
}
これを行うために「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を読み取って変数に割り当てる必要があります。私はあまり熟練していませんが、これは実行可能だと思います。
純粋な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 "$@"
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を選択しました。