Délai d'expiration d'une commande dans bash sans délai inutile

283

Cette réponse à la commande de ligne de commande pour tuer automatiquement une commande après un certain temps

propose une méthode sur une ligne pour expirer une commande de longue durée à partir de la ligne de commande bash:

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

Mais il est possible qu'une commande "de longue durée" donnée se termine plus tôt que le délai d'expiration. (Appelons-la une commande "généralement longue mais parfois rapide" ou tlrbsf pour le plaisir.)

Donc, cette approche astucieuse à 1 doublure a quelques problèmes. Tout d'abord, le sleepn'est pas conditionnel, ce qui définit une limite inférieure indésirable sur le temps nécessaire à la séquence pour se terminer. Considérez 30s ou 2m ou même 5m pour le sommeil, lorsque la commande tlrbsf se termine en 2 secondes - hautement indésirable. Deuxièmement, le killest inconditionnel, donc cette séquence tentera de tuer un processus non en cours et de se plaindre.

Alors...

Existe-t-il un moyen de temporiser une commande généralement longue mais parfois rapide ( "tlrbsf" ) qui

  • a une implémentation bash (l'autre question a déjà des réponses Perl et C)
  • se terminera à la première des deux: fin du programme tlrbsf , ou délai d'expiration
  • ne tuera pas les processus non existants / non en cours d'exécution (ou, facultativement: ne se plaindra pas d'un mauvais kill)
  • ne doit pas être un doublure
  • peut fonctionner sous Cygwin ou Linux

... et, pour les points bonus, exécute la commande tlrbsf au premier plan et tout processus "sleep" ou supplémentaire en arrière-plan, de telle sorte que la commande stdin / stdout / stderr de la commande tlrbsf peut être redirigée, comme si elle avait été courir directement?

Si oui, veuillez partager votre code. Sinon, veuillez expliquer pourquoi.

J'ai passé un certain temps à essayer de pirater l'exemple susmentionné, mais j'atteins la limite de mes compétences bash.

système PAUSE
la source
5
Une autre question similaire: stackoverflow.com/questions/526782/… (mais je pense que la réponse «timeout3» ici est bien meilleure).
système PAUSE
2
Une raison de ne pas utiliser l' timeoututilitaire GNU ?
Chris Johnson
timeoutc'est super! vous pouvez même utiliser avec plusieurs commandes (script multi-lignes): stackoverflow.com/a/61888916/658497
Noam Manos

Réponses:

149

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 "$@"
Juliano
la source
C'est une astuce intéressante, en utilisant $$ pour la partie d'arrière-plan. Et bien qu'il tue immédiatement un tlrbsf bloqué. Mais bon, vous devez choisir un intervalle d'interrogation. Et si vous définissez l'interrogation trop bas, il consommera du CPU avec une signalisation constante, ce qui rendra le tlrbsf encore plus long!
système PAUSE
7
Vous n'avez pas à choisir l'intervalle d'interrogation, il a une valeur par défaut de 1, ce qui est plutôt bien. Et la vérification est très peu coûteuse, les frais généraux sont négligeables. Je doute que cela rendrait tlrbsf sensiblement plus long. J'ai testé avec sleep 30 et j'ai obtenu une différence de 0,000 ms entre son utilisation et sa non-utilisation.
Juliano
6
Bon, je vois ça maintenant. Et cela répond exactement à mes besoins si vous définissez l'intervalle d'interrogation == timeout. Fonctionne également dans les pipelines, fonctionne avec le tout en arrière-plan, fonctionne avec plusieurs instances et d'autres travaux en cours d'exécution. Merci merci!
système PAUSE
L'envoi d'un signal tue le sous-shell, j'ai donc pensé que aligner toutes les commandes kill sur une seule ligne les préserverait. J'ai également activé la sortie stderr pour afficher les erreurs inattendues. stackoverflow.com/questions/687948/…
eel ghEEz
1
@Juliano C'est un excellent moyen de gérer les délais d'attente, très utile. Je me demande s'il existe un moyen de faire en sorte que le script retourne le code de sortie 143 lorsque le processus est tué après expiration du délai? J'ai essayé d'ajouter "exit 143" juste après la commande kill, mais j'obtiens toujours le code de sortie 0 au script de l'appelant.
Salman A. Kagzi
529

Vous recherchez probablement la timeoutcommande dans coreutils. Puisqu'il fait partie de coreutils, c'est techniquement une solution C, mais c'est toujours coreutils. info timeoutpour plus de détails. Voici un exemple:

timeout 5 /path/to/slow/command with options
yingted
la source
21
Sous Mac, vous pouvez l'installer via Macports ou homebrew.
Ivan Z. Siu
23
Une fois installé via homebrew sur OS X la commande devientgtimeout
ethicalhack3r
5
... quel OS utilisez-vous qui a des coreutils d'avant 2003?
Keith
5
@Keith: CentOS 5.10, par exemple :-(
pause jusqu'à nouvel ordre.
9
Pour clarifier, la commande homebrew dont vous avez besoin sur OSX / mac est brew install coreutils, et vous pouvez ensuite l'utiliser gtimeout.
Ohad Schneider
37

Cette solution fonctionne quel que soit le mode de surveillance bash. Vous pouvez utiliser le signal approprié pour terminer votre_commande

#!/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 un délai d'expiration donné; le script attend la tâche lente et termine l'observateur. Notez que waitcela ne fonctionne pas avec les processus qui sont des enfants d'un shell différent.

Exemples:

  • your_command s'exécute plus de 2 secondes et a été interrompue

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 s'est terminée avant la temporisation (20 secondes)

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
Dmitry
la source
1
waitrenvoie l'état de sortie du processus en attente. Donc, si votre commande se termine dans le temps imparti mais avec un état de sortie différent de zéro, la logique ici se comportera comme elle a expiré, c'est-à-dire imprimer your_command interrupted. Au lieu de cela, vous pouvez fairewait sans ifet vérifier si le $watcherpid existe toujours, si c'est le cas, vous savez que vous n'avez pas dépassé le délai d'expiration.
George Hawkins
Je ne peux pas comprendre pourquoi cela utilise killdans un cas, maispkill dans l'autre. J'avais besoin d'utiliser les pkilldeux pour faire fonctionner correctement. Je m'attends à ce que si vous encapsulez une commande (), vous devrez alors l'utiliser pkillpour la tuer. Mais peut-être que cela fonctionne différemment s'il n'y a qu'une seule commande à l'intérieur du ().
nobar
Que Dieu vous bénisse monsieur :)
Darko Miletic
22

Voilà:

timeout --signal=SIGINT 10 /path/to/slow command with options

vous pouvez changer le SIGINTet 10comme vous le souhaitez;)

Aarian P. Aleahmad
la source
3
"timeout" fait partie du package coreutils sur (au moins) Redhat, Centos, Suse et Ubuntu, vous devrez donc l'installer si vous ne l'avez pas.
Akom
c'est vraiment utile !!!!!! Savez-vous pourquoi «timeout 5 / path / to / slow / command with options» de yingted ne fonctionne parfois pas?
Decula
Malheureusement, ce n'est pas dans le coreutilspaquet sur FreeBSD.
Paul Bissex
18

Vous pouvez le faire entièrement avec bash 4.3et au-dessus:

_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; r=$?; kill -9 `jobs -p`; exit $r; ) }
  • Exemple: _timeout 5 longrunning_command args
  • Exemple: { _timeout 5 producer || echo KABOOM $?; } | consumer
  • Exemple: producer | { _timeout 5 consumer1; consumer2; }
  • Exemple: { while date; do sleep .3; done; } | _timeout 5 cat | less

  • Nécessite Bash 4.3 pour wait -n

  • Donne 137 si la commande a été supprimée, sinon la valeur de retour de la commande.
  • Fonctionne pour les tuyaux. (Vous n'avez pas besoin d'aller au premier plan ici!)
  • Fonctionne également avec les commandes ou fonctions internes du shell.
  • Fonctionne dans un sous-shell, donc aucune exportation de variable dans le shell actuel, désolé.

Si vous n'avez pas besoin du code retour, cela peut être rendu encore plus simple:

_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; kill -9 `jobs -p`; ) }

Remarques:

  • Au sens strict , vous n'avez pas besoin ;dans ; ), mais il fait chose plus cohérente à l' ; }-case. Et le set +bpeut aussi être laissé de côté, mais mieux vaut prévenir que guérir.

  • À l'exception de --forground(probablement), vous pouvez implémenter toutes les variantes de timeoutsupports. --preserve-statusest un peu difficile, cependant. Ceci est laissé comme un exercice pour le lecteur;)

Cette recette peut être utilisée "naturellement" dans la coquille (aussi naturelle que pour flock fd):

(
set +b
sleep 20 &
{
YOUR SHELL CODE HERE
} &
wait -n
kill `jobs -p`
)

Cependant, comme expliqué ci-dessus, vous ne pouvez pas réexporter naturellement les variables d'environnement dans le shell englobant de cette façon.

Éditer:

Exemple réel: Délai d'attente __git_ps1au 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: Bugfix. J'ai remarqué que ce exit 137n'est pas nécessaire et rend _timeoutpeu fiable en même temps.

Edit3: gitest un die-hard, il a donc besoin d'un double truc pour fonctionner de manière satisfaisante.

Edit4: Oublié un _dans le premier _timeoutpour l'exemple GIT du monde réel.

Tino
la source
1
Frappez 4 pierres. C'est tout.
système PAUSE
1
Cela nécessite en fait Bash 4.3 ou plus récent. cc. The 'wait' builtin has a new '-n' option to wait for the next child to change status. De: tiswww.case.edu/php/chet/bash/NEWS
Ben Reser
17

Je préfère "timelimit", qui a au moins un paquet dans debian.

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

Il est un peu plus agréable que le "timeout" coreutils car il imprime quelque chose lors de l'arrêt du processus, et il envoie également SIGKILL après un certain temps par défaut.

maxy
la source
Cela ne semble pas assez bien fonctionner: / $ time timelimit -T2 sleep 10 real 0m10.003s user 0m0.000s sys 0m0.000s
hithwen
3
Utilisez -t2 et non -T2. Le grand -T est le temps entre l'envoi de SIGTERM et l'envoi de SIGKILL.
maxy
1
Je voudrais ajouter que timelimit 1.8 ne fonctionne pas bien avec fork ( timelimit -t1 ./a_forking_progne tue qu'un des deux processus), mais le timeout fonctionne.
Jeremy Cochoy
Si vous voulez un délai pour imprimer quelque chose lors de l'arrêt du processus, utilisez simplement l'indicateur "-v".
10

Pour expirer slowcommandaprès 1 seconde:

timeout 1 slowcommand || echo "I failed, perhaps due to time out"

Pour déterminer si la commande a expiré ou a échoué pour ses propres raisons, vérifiez si le code d'état est 124:

# ping for 3 seconds, but timeout after only 1 second
timeout 1 ping 8.8.8.8 -w3
EXIT_STATUS=$?
if [ $EXIT_STATUS -eq 124 ]
then
echo 'Process Timed Out!'
else
echo 'Process did not timeout. Something else went wrong.'
fi
exit $EXIT_STATUS

Notez que lorsque l'état de sortie est 124, vous ne savez pas s'il a expiré en raison de votre timeoutcommande, ou si la commande elle-même s'est terminée en raison d'une logique de temporisation interne qui lui est propre, puis a renvoyé 124. Vous pouvez supposer en toute sécurité dans les deux cas , cependant, qu'un délai d'attente d'une certaine sorte s'est produit.

lance.dolan
la source
9

Voir également le script http://www.pixelbeat.org/scripts/timeout dont la fonctionnalité a été intégrée dans les coreutils plus récents

pixelbeat
la source
Soigné, simple, utilise TERM et non KILL. Agréable! J'avais exploré une solution de piège / attente comme celle-ci lorsque j'ai posé la question à l'origine.
système PAUSE
le délai d'attente renvoie 124 en cas de mise à mort.
ceving
8

Un peu hacky, mais ça marche. Ne fonctionne pas si vous avez d'autres processus de premier plan (veuillez m'aider à 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
strager
la source
(ls -ltR / cygdrive / c / windows & SPID = $ {!}; (sleep 1s; kill $ {SPID}) & CPID = $ {!}; fg 1; kill $ {CPID})> fdsa
système PAUSE
bash: fg: pas de contrôle des travaux
système PAUSE
@system PAUSE, set -m, je pense.
strager
J'ai le contrôle des travaux (set -m) dans le shell de connexion. C'est le «m» dans le contenu himBH de $ -, mais il semble disparaître pour les sous-coquilles. Peut-être un artefact Cygwin. grognement
système PAUSE
N'utilisez pas "fg" dans les scripts. Lisez "help wait".
lhunath
8

le timeout est probablement la première approche à essayer. Il se peut que vous ayez besoin d'une notification ou d'une autre commande pour l'exécuter si elle expire. Après un peu de recherche et d'expérimentation, j'ai trouvé 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
user2099484
la source
5

Script simple avec clarté de 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
$

la source
3

Si vous connaissez déjà le nom du programme (supposons program) de se terminer après le délai d'expiration (par exemple en 3secondes), je peux apporter une solution alternative 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.

loup
la source
2
Cela tue les autres processus qui utilisent le nom et ne tue pas le processus avec le nom donné s'il change de nom (par exemple en écrivant argv[0], peut-être avec d'autres hacks pour faire plus de place).
Jed
J'ai trouvé une variante de cette pratique lorsque j'essaie d'arrêter les conteneurs Docker après un certain temps. Le client docker ne semblait pas accepter TERM / INT / KILL d'une manière qui arrêterait réellement le conteneur qui était exécuté au premier plan. Donc, donner un nom au conteneur et l'utiliser (sleep 3 && docker stop <container>) &a bien fonctionné. Merci!
Mat Schaffer
oui, c'est sale et limité, mais peut être amélioré comme: De { sleep 5 && kill -9 $(ps -fe | grep "program" | grep $$ | tr -s " " | cut -d" " -f2); } & SLEEPPID=$!; bash -c "program" && kill -9 $SLEEPPID"cette façon, il ne tuera que les travaux dans le shell actuel.
tonne
2

Il y a aussi cratimeoutpar 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'
max32
la source
"En particulier, il préserve le comportement du signal." C'est bien d'avoir cette option!
système PAUSE
1

OS X n'utilise pas encore bash 4, ni n'a / usr / bin / timeout, alors voici une fonction qui fonctionne sur OS X sans home-brew ou macports qui est similaire à / usr / bin / timeout (basé sur Tino's répondre). La validation des paramètres, l'aide, l'utilisation et la prise en charge d'autres signaux sont 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
) }
NerdMachine
la source
0

Dans 99% des cas, la réponse n'est PAS d'implémenter une logique de temporisation. La logique de temporisation est dans presque toutes les situations un signe d'avertissement rouge que quelque chose d' autre ne va pas et devrait être corrigé à la place .

Votre processus est-il parfois suspendu ou interrompu après n secondes? Découvrez ensuite pourquoi et corrigez cela à la place.

En passant, pour faire correctement la solution de strager, vous devez utiliser attendre "$ SPID" au lieu de fg 1, car dans les scripts, vous n'avez pas de contrôle de travail (et essayer de l'activer est stupide). De plus, fg 1 repose sur le fait que vous n'avez démarré aucun autre travail auparavant dans le script, ce qui est une mauvaise hypothèse à faire.

lhunath
la source
4
Avec un accès à 100% de la source (et à la plupart du matériel, comme les commutateurs réseau), je conviens qu'il existe probablement de meilleures solutions qu'un délai d'attente. Mais lorsqu'un «tlrbsf» est de source fermée, binaire uniquement, il faut parfois contourner cette limitation.
système PAUSE
@lhunath, "dans les scripts, vous n'avez pas de contrôle des travaux (et essayer de l'activer est stupide)" - Veuillez clarifier ici: stackoverflow.com/questions/690266/…
système PAUSE
@system PAUSE: Répondre stackoverflow.com/questions/690266/… est correct, je l'ai également commenté.
lhunath
29
lhunath, ce que vous dites n'a aucun sens. il y a des tonnes de cas où la temporisation est une bonne option, par exemple chaque fois que vous devez passer par le réseau.
Nate Murray
Une exception: vous écrivez des scripts de test pour vérifier si un logiciel déjà en cours d'exécution n'a pas de problème de timeout.
peterh
0

On m'a présenté un problème pour préserver le contexte du shell et autoriser les délais d'expiration, le seul problème avec cela est qu'il arrêtera l'exécution du script sur le délai d'expiration - mais cela convient aux besoins qui m'ont été présentés:

#!/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 dir appelé scripts

mpapis
la source
0
#! /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 "$@"
anguille ghEEz
la source
@Tino Désolé, j'ai oublié pourquoi j'ai changé la ligne de terminaison du processus et pourquoi j'ai pensé que c'était important de partager. Dommage, je n'ai pas écrit cela. Peut-être, j'ai trouvé que je devais faire une pause avant de vérifier le succès de kill -s TERM. Le script de l'année 2008 du livre de cuisine semble vérifier l'état du processus immédiatement après l'envoi de SIGTERM, ce qui peut entraîner une erreur lors de l'envoi de SIGKILL à un processus mort.
anguille ghEEz
0

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 d'expiration ou le code retour de la commande en cas de succès

Veuillez noter que la suppression des processus d'une session ssh est gérée différemment d'un shell interactif. Mais vous pouvez également utiliser l'option -t pour ssh pour allouer un pseudo terminal, il agit donc comme un shell interactif

Franky_GT
la source
0

Voici une version qui ne repose pas sur la création d'un processus enfant - j'avais besoin d'un script autonome qui incorporait cette fonctionnalité. Il effectue également un intervalle d'interrogation fractionné, de sorte que vous pouvez interroger plus rapidement. le délai 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
Neil McGill
la source
-1

Une manière très simpliste:

# command & sleep 5; pkill -9 -x -f "command"

avec pkill (option -f ), vous pouvez tuer votre commande spécifique avec des arguments ou spécifier -n pour éviter de tuer l'ancien processus.

Francis
la source
Vous vous rendez compte que c'est essentiellement ce que le PO a dans son poste et ce qu'il indique qu'il ne veut pas, non? Parce qu'il attend toujours le retard de sommeil complet.
Etan Reisner
-1

S'appuyer sur la réponse de @ loup ...

Si vous souhaitez expirer un processus et arrêter la sortie du job kill / pid, exécutez:

( (sleep 1 && killall program 2>/dev/null) &) && program --version 

Cela met le processus en arrière-plan dans un sous-shell afin que vous ne voyiez pas la sortie du travail.

Cody A. Ray
la source
-3

J'ai un travail cron qui appelle un script php et, parfois, il se bloque sur le script php. Cette solution était parfaite pour moi.

J'utilise:

scripttimeout -t 60 /script.php
Jehad Buria
la source
1
Qu'est-ce que c'est scripttimeout?
rudolfbyker