Cette réponse à Commande en ligne de commande pour supprimer automatiquement une commande après un certain temps
propose une méthode sur 1 ligne pour expirer une commande longue depuis la ligne de commande bash:
( /path/to/slow command with options ) & sleep 5 ; kill $!
Mais il est possible qu'une commande "longue" donnée se termine avant l'expiration du délai. (Appelons-la une commande "typiquement-longue-courante-mais-parfois-rapide", ou tlrbsf par plaisir.)
Donc, cette approche astucieuse à une ligne a quelques problèmes. Premièrement, la variable sleep
n'est pas conditionnelle, ce qui définit une limite inférieure indésirable sur le temps pris pour terminer la séquence. Considérez 30 ou 2 m, voire 5 m pour le sommeil, lorsque la commande tlrbsf se termine en 2 secondes, ce qui est hautement indésirable. Deuxièmement, la kill
étant inconditionnelle, cette séquence va tenter de tuer un processus qui ne fonctionne pas et de se plaindre.
Alors...
Existe-t-il un moyen de temporiser une commande généralement longue ("tlrbsf")
... et, pour les points bonus, exécute la commande tlrbsf au premier plan et tout processus "veille" ou supplémentaire en arrière-plan, tel que le stdin/stdout/stderr du tlrbsf La commande peut être redirigée, comme si elle avait été lancée directement?
Si oui, merci de partager votre code. Si non, s'il vous plaît expliquer pourquoi.
J'ai passé un certain temps à essayer de pirater l'exemple susmentionné, mais je suis à la limite de mes compétences bash.
Je pense que c'est précisément ce que vous demandez:
http://www.bashcookbook.com/bashinfo/source/bash-4.0/examples/scripts/timeout3
#!/bin/bash
#
# The Bash Shell script executes a command with a time-out.
# Upon time-out expiration SIGTERM (15) is sent to the process. If the signal
# is blocked, then the subsequent SIGKILL (9) terminates it.
#
# Based on the Bash documentation example.
# Hello Chet,
# please find attached a "little easier" :-) to comprehend
# time-out example. If you find it suitable, feel free to include
# anywhere: the very same logic as in the original examples/scripts, a
# little more transparent implementation to my taste.
#
# Dmitry V Golovashkin <[email protected]>
scriptName="${0##*/}"
declare -i DEFAULT_TIMEOUT=9
declare -i DEFAULT_INTERVAL=1
declare -i DEFAULT_DELAY=1
# Timeout.
declare -i timeout=DEFAULT_TIMEOUT
# Interval between checks if the process is still alive.
declare -i interval=DEFAULT_INTERVAL
# Delay between posting the SIGTERM signal and destroying the process by SIGKILL.
declare -i delay=DEFAULT_DELAY
function printUsage() {
cat <<EOF
Synopsis
$scriptName [-t timeout] [-i interval] [-d delay] command
Execute a command with a time-out.
Upon time-out expiration SIGTERM (15) is sent to the process. If SIGTERM
signal is blocked, then the subsequent SIGKILL (9) terminates it.
-t timeout
Number of seconds to wait for command completion.
Default value: $DEFAULT_TIMEOUT seconds.
-i interval
Interval between checks if the process is still alive.
Positive integer, default value: $DEFAULT_INTERVAL seconds.
-d delay
Delay between posting the SIGTERM signal and destroying the
process by SIGKILL. Default value: $DEFAULT_DELAY seconds.
As of today, Bash does not support floating point arithmetic (sleep does),
therefore all delay/time values must be integers.
EOF
}
# Options.
while getopts ":t:i:d:" option; do
case "$option" in
t) timeout=$OPTARG ;;
i) interval=$OPTARG ;;
d) delay=$OPTARG ;;
*) printUsage; exit 1 ;;
esac
done
shift $((OPTIND - 1))
# $# should be at least 1 (the command to execute), however it may be strictly
# greater than 1 if the command itself has options.
if (($# == 0 || interval <= 0)); then
printUsage
exit 1
fi
# kill -0 pid Exit code indicates if a signal may be sent to $pid process.
(
((t = timeout))
while ((t > 0)); do
sleep $interval
kill -0 $$ || exit 0
((t -= interval))
done
# Be Nice, post SIGTERM first.
# The 'exit 0' below will be executed if any preceeding command fails.
kill -s SIGTERM $$ && kill -0 $$ || exit 0
sleep $delay
kill -s SIGKILL $$
) 2> /dev/null &
exec "$@"
Vous recherchez probablement la commande timeout
dans coreutils. Étant donné qu’il fait partie de coreutils, c’est techniquement une solution en C, mais c’est toujours coreutils. info timeout
pour plus de détails ..__ Voici un exemple:
timeout 5 /path/to/slow/command with options
Cette solution fonctionne quel que soit le mode de surveillance bash. Vous pouvez utiliser le bon signal pour terminer your_command
#!/bin/sh
( your_command ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher
L'observateur tue votre_commande après l'expiration du délai imparti; le script attend la tâche lente et met fin à l'observateur. Notez que wait
ne fonctionne pas avec les processus qui sont les enfants d'un autre shell.
votre_commande interrompue
( sleep 20 ) & pid=$!
( sleep 2 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
echo "your_command finished"
pkill -HUP -P $watcher
wait $watcher
else
echo "your_command interrupted"
fi
votre_commande terminée
( sleep 2 ) & pid=$!
( sleep 20 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
echo "your_command finished"
pkill -HUP -P $watcher
wait $watcher
else
echo "your_command interrupted"
fi
Voilà:
timeout --signal=SIGINT 10 /path/to/slow command with options
vous pouvez changer la SIGINT
et le 10
comme vous le souhaitez;)
Je préfère "timelimit", qui contient un paquet au moins dans debian.
http://devel.ringlet.net/sysutils/timelimit/
Il est un peu plus agréable que le délai d'attente de coreutils, car il imprime quelque chose lorsque vous tuez le processus et envoie également SIGKILL après un certain temps par défaut.
Vous pouvez le faire entièrement avec bash 4.3
et plus:
_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; r=$?; kill -9 `jobs -p`; exit $r; ) }
_timeout 5 longrunning_command args
{ _timeout 5 producer || echo KABOOM $?; } | consumer
producer | { _timeout 5 consumer1; consumer2; }
Exemple: { while date; do sleep .3; done; } | _timeout 5 cat | less
Nécessite Bash 4.3 pour wait -n
Si vous n'avez pas besoin du code de retour, vous pouvez le simplifier encore davantage:
_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; kill -9 `jobs -p`; ) }
Remarques:
Strictement parlant, vous n'avez pas besoin du ;
dans ; )
, mais cela rend les choses plus cohérentes avec le ; }
-. Et le set +b
peut probablement être laissé de côté aussi, mais mieux vaut prévenir que guérir.
À l'exception de --forground
(probablement), vous pouvez implémenter toutes les variantes supportées par timeout
. --preserve-status
est un peu difficile, cependant. Ceci est laissé comme un exercice pour le lecteur;)
Cette recette peut être utilisée "naturellement" dans la coque (aussi naturelle que pour flock fd
):
(
set +b
sleep 20 &
{
YOUR Shell CODE HERE
} &
wait -n
kill `jobs -p`
)
Toutefois, comme expliqué ci-dessus, vous ne pouvez pas réexporter naturellement les variables d'environnement dans le Shell englobant.
Modifier:
Exemple concret: Time out __git_ps1
au cas où cela prendrait trop de temps (pour des choses comme les liens SSHFS lents):
eval "__orig$(declare -f __git_ps1)" && __git_ps1() { ( git() { _timeout 0.3 /usr/bin/git "$@"; }; _timeout 0.3 __orig__git_ps1 "$@"; ) }
Edit2: correction de bug. J'ai remarqué que exit 137
n'est pas nécessaire et rend _timeout
non fiable en même temps.
Edit3: git
est un disque dur, il a donc besoin d'un double tour pour fonctionner de manière satisfaisante.
Edit4: Oublié un _
dans le premier _timeout
pour l'exemple GIT du monde réel.
Voir aussi le http://www.pixelbeat.org/scripts/timeout script dont la fonctionnalité a été intégrée dans les nouveaux coreutils.
timeout est probablement la première approche à essayer. Vous aurez peut-être besoin d'une notification ou d'une autre commande à exécuter si le délai est écoulé. Après avoir fait quelques recherches et expérimenté, j'ai créé ce script bash :
if
timeout 20s COMMAND_YOU_WANT_TO_EXECUTE;
timeout 20s AS_MANY_COMMANDS_AS_YOU_WANT;
then
echo 'OK'; #if you want a positive response
else
echo 'Not OK';
AND_ALTERNATIVE_COMMANDS
fi
Un peu hacky, mais ça marche. Ne fonctionne pas si vous avez d'autres processus de premier plan (aidez-moi s'il vous plaît à résoudre ce problème!)
sleep TIMEOUT & SPID=${!}; (YOUR COMMAND HERE; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}
En fait, je pense que vous pouvez l’inverser, en répondant à vos critères de «bonus»:
(YOUR COMMAND HERE & SPID=${!}; (sleep TIMEOUT; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}) < asdf > fdsa
Script simple avec la clarté du code. Enregistrer dans /usr/local/bin/run
:
#!/bin/bash
# run
# Run command with timeout $1 seconds.
# Timeout seconds
timeout_seconds="$1"
shift
# PID
pid=$$
# Start timeout
(
sleep "$timeout_seconds"
echo "Timed out after $timeout_seconds seconds"
kill -- -$pid &>/dev/null
) &
timeout_pid=$!
# Run
"$@"
# Stop timeout
kill $timeout_pid &>/dev/null
Expire une commande trop longue:
$ run 2 sleep 10
Timed out after 2 seconds
Terminated
$
Se termine immédiatement pour une commande qui se termine:
$ run 10 sleep 2
$
Si vous connaissez déjà le nom du programme (supposons que program
) se termine après l'expiration du délai (par exemple 3
secondes), je peux proposer une solution de rechange simple et quelque peu sale:
(sleep 3 && killall program) & ./program
Cela fonctionne parfaitement si j'appelle des processus de référence avec des appels système.
Pour expirer la slowcommand
après 1 seconde:
timeout 1 slowcommand || echo "I failed, perhaps due to time out"
Il y a aussi cratimeout
de Martin Cracauer (écrit en C pour les systèmes Unix et Linux).
# cf. http://www.cons.org/cracauer/software.html
# usage: cratimeout timeout_in_msec cmd args
cratimeout 5000 sleep 1
cratimeout 5000 sleep 600
cratimeout 5000 tail -f /dev/null
cratimeout 5000 sh -c 'while sleep 1; do date; done'
OS X n'utilise pas encore bash 4, pas plus que/usr/bin/timeout. Voici donc une fonction qui fonctionne sous OS X sans home-brass ou macports, similaire à/usr/bin/timeout réponse). La validation des paramètres, l'aide, l'utilisation et la prise en charge d'autres signaux constituent un exercice pour le lecteur.
# implement /usr/bin/timeout only if it doesn't exist
[ -n "$(type -p timeout 2>&1)" ] || function timeout { (
set -m +b
sleep "$1" &
SPID=${!}
("${@:2}"; RETVAL=$?; kill ${SPID}; exit $RETVAL) &
CPID=${!}
wait %1
SLEEPRETVAL=$?
if [ $SLEEPRETVAL -eq 0 ] && kill ${CPID} >/dev/null 2>&1 ; then
RETVAL=124
# When you need to make sure it dies
#(sleep 1; kill -9 ${CPID} >/dev/null 2>&1)&
wait %2
else
wait %2
RETVAL=$?
fi
return $RETVAL
) }
Voici une version qui ne repose pas sur la génération d'un processus enfant. J'avais besoin d'un script autonome intégrant cette fonctionnalité. Il effectue également un intervalle d'interrogation fractionnaire, ce qui vous permet d'interroger plus rapidement. le délai d'attente aurait été préféré - mais je suis bloqué sur un ancien serveur
# wait_on_command <timeout> <poll interval> command
wait_on_command()
{
local timeout=$1; shift
local interval=$1; shift
$* &
local child=$!
loops=$(bc <<< "($timeout * (1 / $interval)) + 0.5" | sed 's/\..*//g')
((t = loops))
while ((t > 0)); do
sleep $interval
kill -0 $child &>/dev/null || return
((t -= 1))
done
kill $child &>/dev/null || kill -0 $child &>/dev/null || return
sleep $interval
kill -9 $child &>/dev/null
echo Timed out
}
slow_command()
{
sleep 2
echo Completed normally
}
# wait 1 sec in 0.1 sec increments
wait_on_command 1 0.1 slow_command
# or call an external command
wait_on_command 1 0.1 sleep 10
J'ai un travail cron qui appelle un script php et, parfois, il reste bloqué sur un script php Cette solution était parfaite pour moi.
J'utilise:
scripttimeout -t 60 /script.php
#! /bin/bash
timeout=10
interval=1
delay=3
(
((t = timeout)) || :
while ((t > 0)); do
echo "$t"
sleep $interval
# Check if the process still exists.
kill -0 $$ 2> /dev/null || exit 0
((t -= interval)) || :
done
# Be Nice, post SIGTERM first.
{ echo SIGTERM to $$ ; kill -s TERM $$ ; sleep $delay ; kill -0 $$ 2> /dev/null && { echo SIGKILL to $$ ; kill -s KILL $$ ; } ; }
) &
exec "$@"
Mon problème était peut-être un peu différent: je lance une commande via ssh sur une machine distante et je veux tuer le shell et les enfants si la commande se bloque.
J'utilise maintenant les éléments suivants:
ssh server '( sleep 60 && kill -9 0 ) 2>/dev/null & my_command; RC=$? ; sleep 1 ; pkill -P $! ; exit $RC'
De cette façon, la commande retourne 255 en cas de dépassement de délai ou le code de retour de la commande en cas de succès.
Veuillez noter que la suppression des processus d'une session ssh est traitée différemment d'un shell interactif. Mais vous pouvez également utiliser l'option -t de ssh pour allouer un pseudo-terminal, de sorte qu'il se comporte comme un shell interactif.
Un problème m'a été présenté pour préserver le contexte du shell et autoriser les délais d'expiration. Le seul problème est que cela arrêtera l'exécution du script à l'expiration du délai imparti.
#!/usr/bin/env bash
safe_kill()
{
ps aux | grep -v grep | grep $1 >/dev/null && kill ${2:-} $1
}
my_timeout()
{
typeset _my_timeout _waiter_pid _return
_my_timeout=$1
echo "Timeout($_my_timeout) running: $*"
shift
(
trap "return 0" USR1
sleep $_my_timeout
echo "Timeout($_my_timeout) reached for: $*"
safe_kill $$
) &
_waiter_pid=$!
"$@" || _return=$?
safe_kill $_waiter_pid -USR1
echo "Timeout($_my_timeout) ran: $*"
return ${_return:-0}
}
my_timeout 3 cd scripts
my_timeout 3 pwd
my_timeout 3 true && echo true || echo false
my_timeout 3 false && echo true || echo false
my_timeout 3 sleep 10
my_timeout 3 pwd
avec les sorties:
Timeout(3) running: 3 cd scripts
Timeout(3) ran: cd scripts
Timeout(3) running: 3 pwd
/home/mpapis/projects/rvm/rvm/scripts
Timeout(3) ran: pwd
Timeout(3) running: 3 true
Timeout(3) ran: true
true
Timeout(3) running: 3 false
Timeout(3) ran: false
false
Timeout(3) running: 3 sleep 10
Timeout(3) reached for: sleep 10
Terminated
bien sûr, je suppose qu'il y avait un répertoire appelé scripts
S'appuyant sur La réponse de @ loup ...
Si vous souhaitez expirer un processus et mettre au silence la sortie du travail temporaire/pid, exécutez:
( (sleep 1 && killall program 2>/dev/null) &) && program --version
Cela place le processus en arrière-plan dans un sous-shell afin que vous ne voyiez pas la sortie du travail.