Bash - limiter les sous-shell en récursivité

Sep 22 2020

J'ai décidé de tester les récursions en bash sur mon serveur de production.

N'exécutez pas ce code sur une machine de production!

#!/usr/bin/bash

function fibo {
    if [ $1 -le 1 ]; then echo $1
    else
        echo $(( $( fibo $(( $1 - 1 )) ) +
            $( fibo $(( $1 - 2 )) )
        ))
    fi
}

fibo 100

Je soupçonnais qu'il serait lent, cependant, je ne m'attendais pas à ce qu'il exécute des dizaines de travaux parallèles. Je pensais que parce que le serveur est multicœur, je serais capable de tuer le processus.

Malheureusement, le serveur s'est figé et la société d'hébergement l'a redémarré.

Existe-t-il un moyen de limiter les travaux parallèles dans bash?

Est-il possible de définir le PID du processus principal dans une minuterie et d'arrêter tous les travaux enfants?

Bien sûr, je demande uniquement à des fins académiques.

Éditer

La version de récursivité Tail fonctionne comme un charme:

#!/usr/bin/bash

fibo() {
    local n=$1 a=$2 b=$3

    case $n in 0) echo "$a" ;;
        1) echo "$b" ;; *) fibo $((n - 1)) "$b" $((a + b)) ;;
    esac
}

fibo "${1:-10}" 0 1

Référence:

time ./fibotr.sh 100

3736710778780434371

real    0m0.078s
user    0m0.000s
sys     0m0.015s

Réponses

1 CharlesDuffy Sep 22 2020 at 11:51

En ce qui concerne les méthodes de prévention, consultez notre site sœur Unix & Linux Stack Exchange . L'une des questions d'actualité qu'ils ont en place est l' exécution d'une bombe à fourche en toute sécurité .

En tant qu'implémentation de votre algorithme d'origine qui ne crée aucun sous-shell, et ne peut donc pas se transformer en une bombe fourche même lorsqu'elle est transmise à un nombre arbitrairement grand:

#!/usr/bin/env bash

declare -g _fibo_resA _fibo_resB # be explicit that it's intentional that these
                                 # variables aren't locals, for future readers
fibo() {
    local _fibo_outvar _fibo_inVal _fibo_locResA _fibo_locResB
    _fibo_outvar=$1; _fibo_inVal=$2
    if [ "$_fibo_inVal" -le 1 ]; then printf -v "$_fibo_outvar" 1
    else
        fibo _fibo_resA "$(( _fibo_inVal - 1 ))"; _fibo_locResA=$_fibo_resA
        fibo _fibo_resB "$(( _fibo_inVal - 2 ))"; _fibo_locResB=$_fibo_resB
        printf -v "$_fibo_outvar" "$(( _fibo_locResA + _fibo_locResB ))"
    fi
}

# note that 100 is way too big; performance breaks down between 20 and 30
# but at least it won't act like a fork bomb!
fibo result "${1:-100}" # let user pass in a value they choose echo "$result"

Pourquoi est-ce écrit ainsi?

  • Le changement le plus important a été d'éliminer $( ), mais cela ne nécessite plus de transmettre la sortie au parent sur stdout. Par conséquent, en utilisant des affectations indirectes à cette fin.
  • L'utilisation de noms de variables à espace de noms (même pour les locaux!) Empêche les conflits lors du renvoi d'un résultat par nom: si l'utilisateur choisit d'écrire un résultat dans un nom de variable correspondant à l'un de nos locaux, leur valeur sera écrite dans le local, pas la variable globale dans laquelle ils veulent placer leur résultat.
  • locResAet locResBsont utilisés parce que nous ne bifurquons pas et ne pouvons pas faire resAet resBlocal; ils empêchent d'autres copies de fibod'écraser les résultats avant que nous puissions les renvoyer.
  • Le funcname() {formulaire de déclaration de syntaxe est utilisé car il function funcname {s'agit d'un ksh-ism. Dans ksh, cependant, ce format change le comportement du code dans le bloc; bash n'honore pas cette intention. Par conséquent, l'utilisation de la compatibilité POSIX funcname() {a moins de surprises pour les personnes venant d'un arrière-plan ksh et améliore la compatibilité de votre code avec d'autres shells POSIX; voirhttps://wiki.bash-hackers.org/scripting/obsolete (notez que la syntaxe de déclaration de fonction est mentionnée à plusieurs endroits).