web-dev-qa-db-fra.com

Exécuter une fonction Shell avec timeout

Pourquoi cela fonctionnerait

timeout 10s echo "foo bar" # foo bar

mais ce ne serait pas

function echoFooBar {
  echo "foo bar"
}

echoFooBar # foo bar

timeout 10s echoFooBar # timeout: failed to run command `echoFooBar': No such file or directory

et comment puis-je le faire fonctionner?

40
speendo

timeout est une commande - elle est donc exécutée dans un sous-processus de votre shell bash. Par conséquent, il n’a pas accès à vos fonctions définies dans votre shell actuel.

La commande timeout est donnée est exécutée en tant que sous-processus de timeout - un processus grand-enfant de votre shell.

Vous pourriez être dérouté parce que echo est à la fois une commande intégrée au shell et une commande distincte.

Ce que vous pouvez faire est de placer votre fonction dans son propre fichier script, chmod pour qu’elle soit exécutable, puis exécutez-la avec timeout.

Vous pouvez également créer un fork, qui exécute votre fonction dans un sous-shell - et dans le processus d'origine, surveille la progression, en supprimant le sous-processus s'il est trop long.

38
Douglas Leeder

Comme le disait Douglas Leeder, vous avez besoin d'un processus distinct pour le délai d'attente. Solution de contournement en exportant la fonction vers des sous-réservoirs et en exécutant le sous-shell manuellement.

export -f echoFooBar
timeout 10s bash -c echoFooBar
28
user3132194

Une alternative en ligne lance également un sous-processus de bash Shell:


timeout 10s bash <<EOT
function echoFooBar {
  echo foo
}

echoFooBar
sleep 20
EOT
20

Vous pouvez créer une fonction qui vous permettrait de faire la même chose que timeout mais aussi pour d’autres fonctions:

function run_cmd { 
    cmd="$1"; timeout="$2";
    grep -qP '^\d+$' <<< $timeout || timeout=10

    ( 
        eval "$cmd" &
        child=$!
        trap -- "" SIGTERM 
        (       
                sleep $timeout
                kill $child 2> /dev/null 
        ) &     
        wait $child
    )
}

Et pourrait fonctionner comme ci-dessous:

run_cmd "echoFooBar" 10

Note: La solution est venue d’une de mes questions: Solution élégante pour implémenter le timeout pour les commandes et fonctions bash

9
Tiago Lopo

si vous souhaitez simplement ajouter timeout en tant qu'option supplémentaire pour l'ensemble du script existant, vous pouvez le tester pour l'option timeout, puis le faire appeler lui-même récursivement sans cette option.

exemple.sh:

#!/bin/bash
if [ "$1" == "-t" ]; then
  timeout 1m $0 $2
else
  #the original script
  echo $1
  sleep 2m
  echo YAWN...
fi

exécuter ce script sans délai d'expiration:

$./example.sh -other_option # -other_option
                            # YAWN...

l'exécuter avec un délai d'une minute:

$./example.sh -t -other_option # -other_option
4
Superole
function foo(){
    for i in {1..100};
    do 
        echo $i;  
        sleep 1;
    done;
}

cat <( foo ) # Will work 
timeout 3 cat <( foo ) # Will Work 
timeout 3 cat <( foo ) | sort # Wont work, As sort will fail 
cat <( timeout 3 cat <( foo ) ) | sort -r # Will Work 
2
Hemant Patel

Cette fonction utilise uniquement les fonctions intégrées

  • Peut-être envisager d'évaluer "$ *" au lieu d'exécuter directement $ @ en fonction de vos besoins

  • Il démarre un travail avec la chaîne de commande spécifiée après le premier argument qui correspond à la valeur de délai d'attente et surveille le pid du travail.

  • Il vérifie toutes les 1 secondes, bash prend en charge les délais d’exécution jusqu’à 0.01, ce qui permet de le modifier.

  • De plus, si votre script nécessite stdin, read doit s'appuyer sur un fd dédié (exec {tofd}<> <(:))

  • Aussi, vous voudrez peut-être modifier le signal d’arrêt (celui à l’intérieur de la boucle) qui est la valeur par défaut de -15, vous pourriez vouloir -9

## forking is evil
timeout() {
    to=$1; shift
    $@ & local wp=$! start=0
     while kill -0 $wp; do
        read -t 1
        start=$((start+1))
        if [ $start -ge $to ]; then
            kill $wp && break
        fi
    done
}
1
untore

En résumé, ma réponse à la réponse de Tiago Lopo est plus lisible:

Je pense qu'il est plus lisible d'imposer un délai d'expiration au sous-shell le plus récent. De cette manière, nous n'avons pas besoin d'évaluer une chaîne et le script entier peut être mis en surbrillance comme Shell par votre éditeur favori. Je mets simplement les commandes après que le sous-shell avec eval se soit créé dans une fonction Shell (testé avec zsh, mais devrait fonctionner avec bash):

timeout_child () {
    trap -- "" SIGTERM
    child=$!
    timeout=$1
    (
            sleep $timeout
            kill $child
    ) &
    wait $child
}

Exemple d'utilisation:

( while true; do echo -n .; sleep 0.1; done) & timeout_child 2

Et de cette façon, cela fonctionne aussi avec une fonction Shell (si elle tourne en arrière-plan):

 print_dots () {
     while true
     do
         sleep 0.1
         echo -n .
     done
 }


 > print_dots & timeout_child 2
 [1] 21725
 [3] 21727
 ...................[1]    21725 terminated  print_dots
 [3]  + 21727 done       ( sleep $timeout; kill $child; )
0
TauPan