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.]
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.
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
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.
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.
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.
La commande timeout d'Ubuntu/Debian une fois compilée à partir des sources pour fonctionner sur Mac. Darwin
10.4. *
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.
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é.
#!/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.
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
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
J'utilise "timelimit", qui est un paquet disponible dans le dépôt debian.
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
}
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.
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 "$@"
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.