Pregunta

Me gustaría matar automáticamente un comando después de un cierto tiempo. Tengo en mente una interfaz como esta:

% constrain 300 ./foo args

Lo que se ejecutaría " ./ foo " con " args " pero elimínelo automáticamente si sigue funcionando después de 5 minutos.

Puede ser útil generalizar la idea a otras restricciones, como la autocompletar un proceso si usa demasiada memoria.

¿Hay alguna herramienta existente que haga eso, o alguien ha escrito algo así?

AGREGADO: la solución de Jonathan es precisamente lo que tenía en mente y funciona como un encanto en Linux, pero no puedo hacer que funcione en Mac OSX. Me deshice de SIGRTMIN, que permite que se compile bien, pero la señal simplemente no se envía al proceso hijo. ¿Alguien sabe cómo hacer que esto funcione en Mac?

[Agregado: tenga en cuenta que hay una actualización disponible de Jonathan que funciona en Mac y en cualquier otro lugar.]

¿Fue útil?

Solución

Llegué bastante tarde a esta fiesta, pero no veo mi truco favorito en las respuestas.

Bajo * NIX, una alarma (2) se hereda en un execve (2) y SIGALRM es fatal de manera predeterminada. Por lo tanto, a menudo puedes simplemente:

$ doalarm () { perl -e 'alarm shift; exec @ARGV' "$@"; } # define a helper function

$ doalarm 300 ./foo.sh args

o instala un trivial C wrapper para hacerlo por ti.

Ventajas Solo un PID está involucrado, y el mecanismo es simple. No matará el proceso incorrecto si, por ejemplo, ./foo.sh se cerró " demasiado rápido " y su PID fue reutilizado. No es necesario que varios subprocesos de shell trabajen en concierto, lo que puede hacerse correctamente, pero es más propenso a la raza.

Desventajas El proceso por tiempo limitado no puede manipular su reloj de alarma (por ejemplo, alarm (2) , ualarm (2) , setitimer (2) ), ya que esto probablemente borraría la alarma heredada. Obviamente, tampoco puede bloquear o ignorar SIGALRM, aunque se puede decir lo mismo de SIGINT, SIGTERM, etc. para algunos otros enfoques.

Algunos sistemas (muy antiguos, creo) implementan sleep (2) en términos de alarm (2) , e incluso hoy en día, algunos programadores usan alarma (2) como un mecanismo de tiempo de espera interno bruto para E / S y otras operaciones. Sin embargo, en mi experiencia, esta técnica es aplicable a la gran mayoría de los procesos que desea limitar en el tiempo.

Otros consejos

GNU Coreutils incluye el comando timeout , instalado por defecto en muchos sistemas.

https://www.gnu.org/software/ coreutils / manual / html_node / timeout-invocation.html

Para ver free -m durante un minuto, elimínelo enviando una señal de TERMINACIÓN:

timeout 1m watch free -m

Tal vez no entiendo la pregunta, pero esto parece factible directamente, al menos en bash:

( /path/to/slow command with options ) & sleep 5 ; kill $!

Esto ejecuta el primer comando, dentro del paréntesis, durante cinco segundos, y luego lo mata. Toda la operación se ejecuta de forma síncrona, es decir, no podrá usar su shell mientras está ocupado esperando el comando lento. Si eso no es lo que querías, debería ser posible agregar otro & amp ;.

La variable $! es una incorporada de Bash que contiene el ID de proceso de la subshell iniciada más recientemente. Es importante no tener el & amp; dentro del paréntesis, al hacerlo se pierde la ID del proceso.

Tengo un programa llamado timeout que hace eso, escrito en C, originalmente en 1989 pero actualizado periódicamente desde entonces.


Actualización: este código no se compila en MacOS X porque SIGRTMIN no está definido y no se agota el tiempo de espera cuando se ejecuta en MacOS X debido a que la función signal () reanuda la wait () después de que la alarma se apaga, lo que no es el comportamiento requerido. Tengo una nueva versión de timeout.c que trata ambos problemas (usando sigaction () en lugar de la señal de () ). Como antes, contácteme para obtener un archivo tar comprimido comprimido de 10 K con el código fuente y una página de manual (vea mi 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 <*>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);
}

Si desea el código 'oficial' para 'stderr.h' y 'stderr.c', contácteme (vea mi perfil).

También hay ulimit, que se puede usar para limitar el tiempo de ejecución disponible para los subprocesos.

ulimit -t 10

Limita el proceso a 10 segundos de tiempo de CPU.

Para usarlo realmente para limitar un proceso nuevo, en lugar del proceso actual, es posible que desee utilizar un script de envoltura:

#! /usr/bin/env python

import os
os.system("ulimit -t 10; other-command-here")

otro-comando puede ser cualquier herramienta. Estaba ejecutando versiones de Java, Python, C y Scheme de diferentes algoritmos de clasificación, y registrando cuánto tiempo tardaron, mientras limitaba el tiempo de ejecución a 30 segundos. Una aplicación de Cocoa-Python generó varias líneas de comando, incluidos los argumentos, y compaginó los tiempos en un archivo CSV, pero en realidad era solo una pelusa en la parte superior del comando proporcionado anteriormente.

Perl one liner, solo para patadas:

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

Esto imprime 'foo' durante diez segundos, luego se apaga. Reemplace '10' con cualquier número de segundos, y 'sí foo' con cualquier comando.

El comando de tiempo de espera de Ubuntu / Debian cuando se compila desde la fuente para trabajar en la Mac. Darwin

10.4. *

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

Mi variación en el perl one-liner le da el estado de salida sin cometer errores con fork () y wait () y sin el riesgo de matar el proceso incorrecto:

#!/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));' "$@"

Básicamente, la bifurcación () y la espera () están ocultas dentro del sistema (). El SIGALRM se entrega al proceso principal, que luego se mata a sí mismo ya su hijo mediante el envío de SIGTERM a todo el grupo de procesos (- $$). En el improbable caso de que el niño salga y el pid del niño se reutilice antes de que ocurra el kill (), esto NO eliminará el proceso incorrecto porque el nuevo proceso con el pid del viejo niño no estará en el mismo proceso del grupo perl proceso .

Como beneficio adicional, la secuencia de comandos también sale con lo que es probablemente el estado de salida correcto.

Prueba 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 $?

Tiene la desventaja de que si el proceso de su pid se reutiliza dentro del tiempo de espera, puede matar el proceso incorrecto. Esto es altamente improbable, pero es posible que esté iniciando más de 20000 procesos por segundo. Esto podría ser arreglado.

Utilizo " timelimit " ;, que es un paquete disponible en el repositorio de 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

El observador mata la tarea lenta después de un tiempo de espera determinado; el script espera la tarea lenta y termina el observador.

Ejemplos:

  • La tarea lenta se ejecutó durante más de 2 segundos y se terminó
  

Tarea lenta interrumpida

( 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 tarea lenta terminó antes del tiempo de espera dado
  

Tarea lenta terminada

( 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

Una ligera modificación del perl one-liner obtendrá el estado de salida correcto.

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

Básicamente, exit ($? > > 8) reenviará el estado de salida del subproceso. Acabo de elegir 77 en el estado de salida para el tiempo de espera.

¿Qué te parece usar la herramienta 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
}

bash puro:


#!/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 "$@"

¿No hay una manera de establecer un tiempo específico con " en " para hacer esto?

$ at 05:00 PM kill -9 $pid

Parece mucho más simple.

Si no sabes cuál va a ser el número pid, asumo que hay una manera de escribirlo con ps aux y grep, pero no estoy seguro de cómo implementarlo.

$   | 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

Tu script tendría que leer el pid y asignarle una variable. No soy demasiado hábil, pero supongo que esto es factible.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top