Comando de linha de comando para auto-kill em um comando depois de um determinado período de tempo

StackOverflow https://stackoverflow.com/questions/601543

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]

Foi útil?

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. *

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

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.

http://devel.ringlet.net/sysutils/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

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top