Comando de linha de comando para auto-kill em um comando depois de um determinado período de tempo
-
03-07-2019 - |
Pergunta
Eu gostaria de matar automaticamente um comando após um determinado período de tempo. Tenho em mente uma interface como este:
% constrain 300 ./foo args
Qual seria executado "./foo" com 'argumentos', mas automaticamente matá-lo se ele ainda está em execução após 5 minutos.
Pode ser útil para generalizar a ideia a outras restrições, como autokilling um processo se ele usa muita memória.
Existem algumas ferramentas existentes que fazem isso, ou alguém por escrito tal coisa?
ADICIONADO: solução de Jonathan é precisamente o que eu tinha em mente e ele funciona como um encanto no linux, mas não posso fazê-lo funcionar no Mac OSX. Eu me livrei do SIGRTMIN que permite que ele fina compilação, mas o sinal simplesmente não são enviados para o processo filho. Alguém sabe como fazer este trabalho no Mac?
[Adicionado:. Note-se que uma atualização está disponível a partir de Jonathan que funciona em Mac e em outros lugares]
Solução
eu cheguei um pouco tarde para essa festa, mas eu não vejo o meu truque favorito listados nas respostas.
Sob * NIX, um alarm(2)
é herdada através de uma execve(2)
e SIGALRM é fatal por padrão. Assim, você pode muitas vezes simplesmente:
$ doalarm () { perl -e 'alarm shift; exec @ARGV' "$@"; } # define a helper function
$ doalarm 300 ./foo.sh args
ou instalar um trivial C invólucro para fazer isso por você.
Vantagens Apenas um PID está envolvida, eo mecanismo é simples. Você não vai matar o processo errado se, por exemplo, ./foo.sh
saiu "muito rapidamente" e seu PID foi re-utilizado. Você não precisa de vários subprocessos shell trabalhando em conjunto, o que pode ser feito corretamente, mas é bastante raça propensa.
Desvantagens O processo de restrição de tempo não pode manipular seu despertador (por exemplo, alarm(2)
, ualarm(2)
, setitimer(2)
), uma vez que este seria provavelmente limpar o alarme herdada. Obviamente, nem pode bloquear ou ignorar SIGALRM, embora o mesmo pode ser dito de SIGINT, SIGTERM, etc. para algumas outras abordagens.
Alguns (muito velho, eu acho) sistemas de implementar sleep(2)
em termos de alarm(2)
, e, ainda hoje, alguns programadores usam alarm(2)
como um mecanismo de tempo limite interno bruto para I / O e outras operações. Na minha experiência, no entanto, esta técnica é aplicável para a grande maioria dos processos que deseja limite de tempo.
Outras dicas
GNU Coreutils inclui o tempo limite comando, instalado por padrão em muitos sistemas.
https://www.gnu.org/software/ coreutils / manual / html_node / timeout-invocation.html
Para assistir free -m
por um minuto, e depois matá-lo enviando um sinal TERM:
timeout 1m watch free -m
Talvez eu não estou entendendo a pergunta, mas isso soa factível diretamente, pelo menos em bash:
( /path/to/slow command with options ) & sleep 5 ; kill $!
Isso executa o primeiro comando, dentro dos parênteses, por cinco segundos, e depois mata-lo. Toda a operação é executada de forma síncrona, ou seja, você não será capaz de usar seu escudo enquanto ele está ocupado espera para o comando lento. Se isso não o que você queria é, deve ser possível adicionar outro &.
A variável $!
é um embutido Bash que contém o ID do processo do subshell iniciado mais recentemente. É importante não ter o & dentro dos parênteses, fazê-lo dessa forma perde o ID do processo.
Eu tenho um programa chamado timeout
que faz isso -. Escrito em C, originalmente em 1989, mas atualizada periodicamente desde então
Update: este código não compilar em MacOS X porque SIGRTMIN não está definido, e não tempo limite quando executado em MacOS X porque a função
signal()
não retoma a wait()
após as horas de alarme - o que não é o comportamento exigido. Eu tenho uma nova versão do timeout.c
que lida com ambos os problemas (usando sigaction()
vez de signal()
). Como antes, o contato Minha 10K gzipped arquivo tar com o código fonte e uma página do manual (ver o meu perfil).
/*
@(#)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);
}
Se você deseja que o código 'oficial' para 'stderr.h' e 'stderr.c', contacte-me (ver o meu perfil).
Há também ulimit é, que pode ser usado para limitar o tempo de execução disponível para sub-processos.
ulimit -t 10
Limita o processo para 10 segundos de tempo de CPU.
Para realmente usá-lo para limitar um novo processo, em vez do processo atual, você pode querer usar um script:
#! /usr/bin/env python
import os
os.system("ulimit -t 10; other-command-here")
outro comando pode ser qualquer ferramenta. Eu estava correndo um versões Java, Python, C e Esquema de diferentes algoritmos de ordenação, e registrar quanto tempo eles levaram, embora limitando o tempo de execução de 30 segundos. Uma aplicação Cacau-Python gerado as várias linhas de comando - incluindo os argumentos -. e coligidos os tempos em um arquivo CSV, mas foi realmente apenas fluff no topo do comando acima, desde
Perl um forro, apenas por diversão:
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
Isto imprime 'foo' por dez segundos, depois expire. Substitua '10' com qualquer número de segundos, e 'sim foo' com qualquer comando.
O comando tempo limite a partir do Ubuntu / Debian quando compilado da fonte para o trabalho no Mac. Darwin
10.4. *
Meu variação sobre o perl one-liner dá-lhe o status de saída sem mucking com fork () e wait () e sem o risco de matar o processo errado:
#!/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));' "$@"
Basicamente, o fork () e wait () estão escondidos sistema dentro (). O SIGALRM é entregue para o processo pai, que depois mata em si e seu filho enviando SIGTERM para todo o grupo de processos (- $$). No caso improvável de que a criança saídas e pid da criança é reutilizada antes de o matar () ocorre, isso não vai matar o processo errado, porque o novo processo com pid do velho filho não vai estar no mesmo grupo de processos do processo perl pai .
Como um benefício adicional, o script também sai com o que é provavelmente o estado de saída correta.
Tente algo como:
# 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 $?
Ele tem a desvantagem de que, se pid seu processo é reutilizada dentro do tempo limite, pode matar o processo errado. Isto é altamente improvável, mas você pode estar começando 20000+ processos por segundo. Isso poderia ser corrigido.
Eu uso "timelimit", que é um pacote disponível no repositório 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
O observador mata a tarefa lenta após dado tempo limite; as esperas de script para a tarefa lenta e termina o observador.
Exemplos:
- A tarefa lenta executar mais de 2 segundos e foi encerrado
tarefa lenta interrompido
( 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
- Esta tarefa lenta terminou antes do tempo limite determinado
tarefa lenta terminado
( 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
A ligeira modificação do perl one-liner vai ter o direito estado de saída.
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
Basicamente, exit ($? >> 8) irá encaminhar o estado de saída do subprocesso. Eu só escolhi 77 no estado de saída para o tempo limite.
Que tal usar a ferramenta esperar?
## 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
}
festa puro:
#!/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 "$@"
Não há uma maneira de definir um tempo específico com "at" para fazer isso?
$ at 05:00 PM kill -9 $pid
Parece muito mais simples.
Se você não sabe o que o número pid vai ser, eu supor que há uma maneira de script de lê-lo com ps aux e grep, mas não tenho certeza como implementar isso.
$ | 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
Seu script teria que ler o pid e atribuir-lhe uma variável. Eu não estou excessivamente qualificados, mas suponho que este é factível.