一定時間後にコマンドを自動キルするコマンドラインコマンド
-
03-07-2019 - |
質問
一定時間後にコマンドを自動的に強制終了したい。私はこのようなインターフェースを念頭に置いています:
% constrain 300 ./foo args
" ./ foo"を実行します" args" 5分経っても実行されている場合は自動的に強制終了します。
メモリを大量に使用している場合のプロセスの自動キルなど、他の制約に概念を一般化すると便利な場合があります。
それを行う既存のツールはありますか、または誰かがそのようなことを書いていますか?
追加:Jonathanのソリューションはまさに私が念頭に置いていたものであり、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ラッパーをインストールして、それを実行します。
利点関係するPIDは1つだけで、メカニズムは単純です。たとえば、 ./ foo.sh
が「あまりにも早く」終了した場合、間違ったプロセスを強制終了することはありません。そのPIDは再利用されました。協調して動作するいくつかのシェルサブプロセスは必要ありません。これは正しく実行できますが、人種が多い傾向があります。
短所時間制約のあるプロセスは、目覚まし時計を操作できません(たとえば、 alarm(2)
、 ualarm(2)
、 setitimer(2)
)。これは継承されたアラームをクリアする可能性が高いためです。明らかに、SIGALRMをブロックしたり無視したりすることはできませんが、他のアプローチではSIGINT、SIGTERMなどについても同じことが言えます。
一部の(非常に古いと思う)システムは、 alarm(2)
の観点から sleep(2)
を実装しており、今日でも一部のプログラマーは I / Oおよびその他の操作のための粗い内部タイムアウトメカニズムとしてのalarm(2)
。しかし、私の経験では、この手法は、時間制限したいプロセスの大部分に適用できます。
他のヒント
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が失われます。
これを行う timeout
というプログラムがあります-1989年にCで書かれていましたが、それ以降定期的に更新されています。
更新:SIGRTMINが定義されていないため、このコードはMacOS Xでのコンパイルに失敗し、MacOS Xでの実行時に
signal()
関数が wait()
アラームがタイムアウトした後-これは必須の動作ではありません。これらの両方の問題を処理する 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 <*>quot;;
#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; /* Dunno what happened! */
else if (WIFEXITED(status))
status = WEXITSTATUS(status);
else if (WIFSIGNALED(status))
status = WTERMSIG(status);
else
status = 2; /* Dunno what happened! */
return(status);
}
「stderr.h」および「stderr.c」の「公式」コードが必要な場合は、ご連絡ください(私のプロフィールを参照)。
ulimitもあります。これは、サブプロセスで使用可能な実行時間を制限するために使用できます。
ulimit -t 10
プロセスを10秒のCPU時間に制限します。
現在のプロセスではなく、実際にそれを使用して新しいプロセスを制限するには、ラッパースクリプトを使用できます。
#! /usr/bin/env python
import os
os.system("ulimit -t 10; other-command-here")
other-commandは任意のツールです。さまざまなソートアルゴリズムのJava、Python、C、およびSchemeバージョンを実行し、実行時間を30秒に制限しながら、それらにかかった時間を記録していました。 Cocoa-Pythonアプリケーションは、さまざまなコマンドライン(引数を含む)を生成し、時間をCSVファイルに照合しましたが、実際には上記のコマンドの上にふわふわしていました。
キック用のライナー1つをパール:
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」を任意のコマンドで置き換えます。
Macで動作するようにソースからコンパイルされたUbuntu / Debianからのタイムアウトコマンド。ダーウィン
10.4。*
perl one-linerの私のバリエーションは、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プロセスの同じプロセスグループにないため、これは間違ったプロセスを強制終了しません。
追加の利点として、スクリプトはおそらく正しい終了ステータスで終了します。
次のようなものを試してください:
# 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以上のプロセスを開始している可能性があります。これは修正できます。
&quot; timelimit&quot;を使用します。これはdebianリポジトリで利用可能なパッケージです。
#!/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
perl one-linerを少し変更すると、終了ステータスが正しくなります。
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($?&gt;&gt; 8)はサブプロセスの終了ステータスを転送します。タイムアウトの終了ステータスで77を選択しました。
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
}
pure bash:
#!/bin/bash
if [[ $# < 2 ]]; then
echo "Usage: <*> timeout cmd [options]"
exit 1
fi
TIMEOUT="$1"
shift
BOSSPID=$
(
sleep $TIMEOUT
kill -9 -$BOSSPID
)&
TIMERPID=$!
trap "kill -9 $TIMERPID" EXIT
eval "$@"
「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を読み取り、変数に割り当てる必要があります。 私はあまり熟練していませんが、これが実行可能であると仮定します。