web-dev-qa-db-fra.com

Commande en ligne de commande pour tuer automatiquement une commande après un certain temps

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

% constrain 300 ./foo args

Ce qui exécuterait "./foo" avec "args" mais le tuerait automatiquement s'il est toujours en cours d'exécution après 5 minutes.

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

Y a-t-il des outils existants qui font cela, ou quelqu'un a-t-il écrit une telle chose?

AJOUTÉ: La solution de Jonathan est précisément ce que j'avais à l'esprit et cela fonctionne comme un charme sur linux, mais je ne peux pas le faire fonctionner sur Mac OSX. Je me suis débarrassé du SIGRTMIN qui lui permet de compiler correctement, 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 qui fonctionne sur Mac et ailleurs.]

52
dreeves

Je suis arrivé assez tard pour cette fête, mais je ne vois pas mon astuce préférée dans les réponses.

Sous * NIX, une alarm(2) est héritée à travers une execve(2) et SIGALRM est fatal par défaut. Ainsi, 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 pour vous.

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

Inconvénients Le processus limité dans le temps ne peut pas manipuler son réveil (par exemple, alarm(2), ualarm(2), setitimer(2)), car cela effacerait probablement l'alarme héritée. De toute évidence, il ne peut pas non plus bloquer ou ignorer SIGALRM, bien que la même chose puisse être dite 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) comme un mécanisme de temporisation interne grossier pour 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.

27
pilcrow

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

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

timeout 1m watch free -m
45
Roger Dahl

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

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

Cela exécute la première commande, entre parenthèses, pendant cinq secondes, puis la tue. L'opération entière s'exécute de manière synchrone, c'est-à-dire que vous ne pourrez pas utiliser votre Shell tant qu'il est occupé à attendre la commande lente. Si ce n'est pas ce que vous vouliez, il devrait être possible d'en ajouter un autre.

Le $! variable est un Bash intégré qui contient l'ID de processus du dernier sous-shell démarré. Il est important de ne pas avoir le & à l'intérieur de la parenthèse, le faire de cette façon perd l'ID de processus.

30
unwind

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

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")

l'autre commande peut être n'importe quel outil. J'exécutais une version Java, Python, C et Scheme de différents algorithmes de tri, et j'enregistrais le temps nécessaire, 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 rassemblé les heures dans un fichier CSV, mais c'était vraiment juste duveteux en plus de la commande fournie ci-dessus.

11

Ma variation sur le Perl one-liner vous donne le statut de sortie sans déblayage 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 livré au processus parent qui se tue lui-même et son enfant en envoyant SIGTERM à l'ensemble du groupe de processus (- $$). Dans le cas peu probable où l'enfant se termine et que le pid de l'enfant est réutilisé avant que le kill () ne se produise, cela ne tuera PAS le mauvais processus car le nouveau processus avec le pid de l'ancien enfant ne sera pas dans le même groupe de processus du processus Perl parent .

Comme avantage supplémentaire, le script se ferme également avec ce qui est probablement l'état de sortie correct.

4
bjaspan

La commande timeout d'Ubuntu/Debian une fois compilée à partir des sources pour fonctionner sur Mac. Darwin

10.4. *

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

4
sheki

Perl one liner, juste pour les coups de pied:

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

Cela imprime "foo" pendant dix secondes, puis expire. Remplacez "10" par un nombre quelconque de secondes et "yes foo" par n'importe quelle commande.

4
vasi

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

Il a l'inconvénient que si le pid de votre processus est réutilisé dans le délai imparti, il peut tuer le mauvais processus. C'est très peu probable, mais vous pouvez démarrer 20000+ processus par seconde. Cela pourrait être corrigé.

2
fadedbee
#!/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 tue la tâche lente après un 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 s'est terminé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

Tâche lente 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
2
Dmitry

J'utilise "timelimit", qui est un paquet disponible dans le dépôt debian.

http://devel.ringlet.net/sysutils/timelimit/

2
maxy

Que diriez-vous d'utiliser l'outil 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
}
1
user276283

N'y a-t-il pas un moyen de définir une heure spécifique avec "at" pour ce faire?

$ at 05:00 PM kill -9 $pid

Semble beaucoup plus simple.

Si vous ne savez pas quel va être le nombre pid, je suppose qu'il existe un moyen de le lire par script avec ps aux et grep, mais vous ne savez pas comment l'implémenter.

$   | 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 affecter une variable. Je ne suis pas trop qualifié, mais je suppose que c'est faisable.

1
tonybaldwin

bash pur:


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

Une légère modification de la doublure Perl obtiendra le bon statut de sortie.

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) transmet l'état de sortie du sous-processus. Je viens de choisir 77 à l'état de sortie pour la temporisation.

1
user143785