Bash - limiter les sous-shell en récursivité
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
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.
locResAetlocResBsont utilisés parce que nous ne bifurquons pas et ne pouvons pas faireresAetresBlocal; ils empêchent d'autres copies defibod'écraser les résultats avant que nous puissions les renvoyer.- Le
funcname() {formulaire de déclaration de syntaxe est utilisé car ilfunction 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é POSIXfuncname() {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).