일정 시간 후에 명령을 자동으로 키우는 명령 행 명령
-
03-07-2019 - |
문제
일정 시간이 지나면 자동으로 명령을 죽이고 싶습니다. 다음과 같은 인터페이스를 염두에두고 있습니다.
% constrain 300 ./foo args
"args"와 함께 "./foo"가 작동하지만 5 분 후에도 여전히 실행되면 자동으로 죽입니다.
너무 많은 메모리를 사용하는 경우 Autokilling A 프로세스와 같은 다른 제약으로 아이디어를 일반화하는 것이 유용 할 수 있습니다.
그렇게하는 기존 도구가 있습니까, 아니면 그런 것을 쓴 사람이 있습니까?
추가 : Jonathan의 솔루션은 정확히 내가 생각했던 것이며 Linux의 매력처럼 작동하지만 Mac OSX에서 작업 할 수는 없습니다. Sigrtmin을 제거하여 정상으로 컴파일 할 수 있지만 신호는 아동 프로세스로 보내지 않습니다. Mac 에서이 작업을 수행하는 방법을 아는 사람이 있습니까?
추가 : Mac과 다른 곳에서 작동하는 Jonathan에서 업데이트를 사용할 수 있습니다.
해결책
나는이 파티에 오히려 늦게 도착했지만 답에 내가 가장 좋아하는 트릭이 보이지 않습니다.
*nix, an alarm(2)
AN을 가로 질러 상속됩니다 execve(2)
Sigalrm은 기본적으로 치명적입니다. 따라서 종종 간단히 할 수 있습니다.
$ doalarm () { perl -e 'alarm shift; exec @ARGV' "$@"; } # define a helper function
$ doalarm 300 ./foo.sh args
또는 설치 a 사소한 C 래퍼 당신을 위해 그렇게합니다.
장점 하나의 PID 만 관련되어 있으며 메커니즘은 간단합니다. 예를 들어, 당신은 잘못된 과정을 죽이지 않을 것입니다. ./foo.sh
"너무 빨리"종료되었고 PID가 재사용되었습니다. 콘서트에서 작동하는 몇 개의 쉘 하위 프로세스가 필요하지 않으며, 올바르게 수행 할 수는 있지만 오히려 레이스가 발생하기 쉽습니다.
단점 시간이 제한된 프로세스는 알람 시계를 조작 할 수 없습니다 (예 : alarm(2)
, ualarm(2)
, setitimer(2)
), 이것은 상속 된 경보를 제거 할 가능성이 있기 때문입니다. 분명히 Sigalrm을 차단하거나 무시할 수는 없지만 Sigint, Sigterm 등에 대해서도 동일하게 말할 수 있습니다. 다른 접근법.
일부 (아주 오래된) 시스템 구현 sleep(2)
측면에서 alarm(2)
, 그리고 오늘날에도 일부 프로그래머는 사용합니다 alarm(2)
I/O 및 기타 작업을위한 조잡한 내부 시간 초과 메커니즘으로. 그러나 내 경험상,이 기술은 시간 제한을 원하는 대부분의 프로세스에 적용 할 수 있습니다.
다른 팁
GNU Coreutils가 포함됩니다 시간 초과 많은 시스템에서 기본적으로 설치된 명령.
https://www.gnu.org/software/coreutils/manual/html_node/timeout-invocation.html
시청합니다 free -m
1 분 동안 용어 신호를 보내서 죽여야합니다.
timeout 1m watch free -m
어쩌면 나는 질문을 이해하지 못할 수도 있지만, 적어도 Bash에서 직접 할 수 있습니다.
( /path/to/slow command with options ) & sleep 5 ; kill $!
이것은 괄호 안에있는 첫 번째 명령을 5 초 동안 실행 한 다음 죽인다. 전체 작업은 동시에 실행됩니다. 즉, 느린 명령을 기다리는 동안 바쁘게 쉘을 사용할 수 없습니다. 그것이 당신이 원했던 것이 아니라면, 다른 것을 추가 할 수 있어야합니다.
그만큼 $!
변수는 가장 최근에 시작된 서브 쉘의 프로세스 ID를 포함하는 Bash 내장입니다. 괄호 안에 & 안에는 그렇지 않으므로 프로세스 ID를 잃는 것이 중요합니다.
나는 프로그램이 있습니다 timeout
C는 원래 1989 년에 작성되었지만 그 이후로 정기적으로 업데이트되었습니다.
업데이트 : Sigrtmin이 정의되지 않았기 때문에이 코드는 MacOS X에서 컴파일하지 못하고 MacOS X에서 실행될 때 타임 아웃에 실패합니다.
signal()
기능이 재개됩니다 wait()
경보가 끝난 후 - 필요한 동작이 아닙니다. 새 버전이 있습니다 timeout.c
이 두 가지 문제를 다루는 것 (사용 sigaction()
대신에 signal()
). 이전과 마찬가지로 소스 코드와 수동 페이지가 포함 된 10K Gzipped 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; /* 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
프로세스를 CPU 시간의 10 초로 제한합니다.
실제로이를 사용하여 현재 프로세스가 아닌 새 프로세스를 제한하려면 래퍼 스크립트를 사용할 수 있습니다.
#! /usr/bin/env python
import os
os.system("ulimit -t 10; other-command-here")
다른 명령은 모든 도구 일 수 있습니다. Java, Python, C 및 다양한 분류 알고리즘의 체계 버전을 실행하고 실행 시간을 30 초로 제한하는 동안 얼마나 오래 걸렸는지 로깅을했습니다. Cocoa -Python 애플리케이션은 인수를 포함하여 다양한 명령 줄을 생성하고 Times를 CSV 파일로 만듭니다. 그러나 실제로 위에서 제공 한 명령 위에는 보풀이있었습니다.
킥을위한 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
이이 이것은 10 초 동안 'foo'를 인쇄 한 다음 시간을 끄는다. '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을 보내서 스스로와 자녀를 죽입니다. 어린이가 킬이 나오기 전에 자녀가 나가고 자녀의 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가 타임 아웃 내에 재사용되면 잘못된 프로세스를 죽일 수 있다는 단점이 있습니다. 이것은 거의 가능하지 않지만 초당 200000+ 프로세스를 시작할 수 있습니다. 이것은 고정 될 수 있습니다.
데비안 저장소에서 사용할 수있는 패키지 인 "Timelimit"을 사용합니다.
#!/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
기본적으로 종료 ($? >> 8)는 하위 프로세스의 종료 상태를 전달합니다. 방금 타임 아웃 출구 상태에서 77을 선택했습니다.
기대 도구를 사용하는 것은 어떻습니까?
## 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
}
순수한 배쉬 :
#!/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 "$@"
이 작업을 수행하기 위해 "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를 읽고 변수를 할당해야합니다. 나는 지나치게 숙련되지는 않지만 이것이 가능하다고 가정합니다.