Question

Je souhaite supprimer automatiquement une commande après un certain temps. Je pense à une interface comme celle-ci:

% constrain 300 ./foo args

Qui exécuterait " ./ foo " avec " args " mais tuez-le automatiquement s'il fonctionne encore après 5 minutes.

Il peut être utile de généraliser l'idée à d'autres contraintes, telles que le remplissage automatique d'un processus s'il utilise trop de mémoire.

Existe-t-il des outils existants ou quelqu'un l'a-t-il écrit?

ADDED: La solution de Jonathan correspond précisément à ce que j'avais en tête. Elle fonctionne à merveille sous Linux, mais je ne parviens pas à la faire fonctionner sous Mac OSX. Je me suis débarrassé de SIGRTMIN qui permet une bonne compilation, mais le signal n'est tout simplement pas envoyé au processus enfant. Quelqu'un sait comment faire fonctionner cela sur Mac?

[Ajouté: Notez qu'une mise à jour est disponible auprès de Jonathan et fonctionne sur Mac et ailleurs.]

Était-ce utile?

La solution

Je suis arrivé assez tard pour cette soirée, mais je ne vois pas mon tour préféré énuméré dans les réponses.

Sous * NIX, un alarm (2) est hérité par un execve (2) et SIGALRM est fatal par défaut. Donc, vous pouvez souvent simplement:

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

$ doalarm 300 ./foo.sh args

ou installez un wrapper C trivial pour le faire à votre place.

Avantages Un seul PID est impliqué et le mécanisme est simple. Vous ne supprimerez pas le mauvais processus si, par exemple, ./foo.sh a quitté "trop ??rapidement". et son PID a été réutilisé. Vous n'avez pas besoin de plusieurs sous-processus de shell travaillant en concert, ce qui peut être fait correctement mais est plutôt sujet à la race.

Inconvénients Le processus limité dans le temps ne peut pas manipuler son réveil (par exemple, alarme (2) , ualarm (2) , setitimer (2) ), car cela effacerait probablement l'alarme héritée. Évidemment, il ne peut pas non plus bloquer ou ignorer SIGALRM, bien que l'on puisse en dire autant de SIGINT, SIGTERM, etc. pour certaines autres approches.

Certains systèmes (très anciens, je pense) implémentent sleep (2) en termes de alarm (2) , et même aujourd'hui, certains programmeurs utilisent . alarm (2) en tant que mécanisme de temporisation interne brut pour les E / S et autres opérations. D'après mon expérience, cependant, cette technique est applicable à la grande majorité des processus que vous souhaitez limiter dans le temps.

Autres conseils

GNU Coreutils inclut la commande timeout , installée par défaut sur de nombreux systèmes.

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

Pour regarder free -m pendant une minute, puis tuez-le en envoyant un signal TERM:

timeout 1m watch free -m

Peut-être que je ne comprends pas la question, mais cela semble faisable directement, du moins en bash:

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

Ceci exécute la première commande, à l'intérieur de la parenthèse, pendant cinq secondes, puis la tue. L’ensemble de l’opération est synchrone, c’est-à-dire que vous ne pourrez pas utiliser votre shell tant que celui-ci est occupé à attendre la commande lente. Si ce n'est pas ce que vous souhaitiez, il devrait être possible d'ajouter un autre & amp;.

La variable $! est une commande intégrée à Bash qui contient l'ID de processus du sous-shell démarré le plus récemment. Il est important de ne pas avoir le & amp; à l'intérieur de la parenthèse, ce faisant, perd l'identifiant du processus.

J'ai un programme appelé timeout qui le fait - écrit en C, à l'origine en 1989, mais mis à jour périodiquement depuis.


Mise à jour: la compilation de ce code sur MacOS X échoue car SIGRTMIN n'est pas défini et son délai d'expiration n'est pas dépassé lorsqu'il est exécuté sur MacOS X car la fonction signal () y reprend le wait () après la fin du délai d'alarme - ce qui n'est pas le comportement requis. J'ai une nouvelle version de timeout.c qui traite de ces deux problèmes (en utilisant sigaction () au lieu de signal () ). Comme auparavant, contactez-moi pour un fichier tar 10K compressé avec le code source et une page de manuel (voir mon profil).

/*
@(#)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 vous souhaitez le code "officiel" pour "stderr.h" et "stderr.c", contactez-moi (voir mon profil).

Il existe également ulimit, qui peut être utilisé pour limiter le temps d'exécution disponible pour les sous-processus.

ulimit -t 10

Limite le processus à 10 secondes de temps CPU.

Pour l'utiliser réellement pour limiter un nouveau processus plutôt que le processus actuel, vous pouvez utiliser un script wrapper:

#! /usr/bin/env python

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

autre-commande peut être n’importe quel outil. J'utilisais des versions Java, Python, C et Scheme de différents algorithmes de tri et enregistrais leur temps, tout en limitant le temps d'exécution à 30 secondes. Une application Cocoa-Python a généré les différentes lignes de commande, y compris les arguments, et les a classées dans un fichier CSV, mais il ne s’agissait que de faire disparaître la commande fournie ci-dessus.

Perl one liner, rien que pour ça:

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

Ceci imprime 'foo' pendant dix secondes, puis expire. Remplacez "10" par un nombre quelconque de secondes et par "yes foo" par une commande.

La commande timeout de Ubuntu / Debian lors de la compilation à partir du source pour fonctionner sur le Mac. Darwin

10.4. *

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

Ma variante sur perl one-liner vous donne le statut de sortie sans passer à autre chose avec fork () et wait () et sans risque de tuer le mauvais processus:

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

Fondamentalement, fork () et wait () sont cachés dans system (). Le SIGALRM est remis au processus parent qui se tue lui-même et son fils en envoyant SIGTERM à l'ensemble du groupe de processus (- $$). Dans le cas improbable où l'enfant quitterait et que le pid de l'enfant serait réutilisé avant que le kill () ne se produise, cela ne tuerait PAS le mauvais processus car le nouveau processus avec le pid de l'ancien enfant ne fera pas partie du même groupe de processus que le processus parent perl. .

Comme avantage supplémentaire, le script se termine également avec probablement le statut de sortie correct.

Essayez quelque chose comme:

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

L’inconvénient est que, si le pid de votre processus est réutilisé dans le délai imparti, il risque de tuer le mauvais processus. Ceci est hautement improbable, mais vous pouvez démarrer plus de 20000 processus par seconde. Cela pourrait être corrigé.

J'utilise "timelimit", un paquet disponible dans le référentiel 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

L’observateur supprime la tâche lente après le délai imparti; le script attend la tâche lente et termine l'observateur.

Exemples:

  • La tâche lente a duré plus de 2 secondes et a été arrêtée
  

Tâche lente interrompue

( 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
  • Cette tâche lente s'est terminée avant le délai imparti
  

La tâche lente est terminée

( 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

Une légère modification de Perl one-liner obtiendra le statut de sortie correct.

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

Fondamentalement, exit ($?> 8> 8) transmettra l'état de sortie du sous-processus. Je viens de choisir 77 au statut de sortie pour le délai d'expiration.

Pourquoi ne pas utiliser l'outil prévu?

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

N’y at-il pas moyen de définir une heure précise avec " at " faire cela?

$ at 05:00 PM kill -9 $pid

Cela semble beaucoup plus simple.

Si vous ne savez pas quel sera le nombre de pid, je suppose qu'il existe un moyen de le lire avec ps aux et grep, mais vous ne savez pas comment le mettre en œuvre.

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

Votre script devrait lire le pid et lui attribuer une variable. Je ne suis pas trop qualifié, mais je suppose que c'est faisable.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top