Comment se fait-il que la bombe d'obus typique s'appelle deux fois?

15

Après avoir parcouru les fameuses questions de Fork Bomb sur Askubuntu et de nombreux autres sites Stack Exchange, je ne comprends pas très bien ce que tout le monde dit comme c'est évident.

De nombreuses réponses ( meilleur exemple ) disent ceci:

" {:|: &}signifie exécuter la fonction :et envoyer à nouveau sa sortie à la :fonction"

Eh bien, quelle est exactement la sortie de :? Qu'est-ce qui est transmis à l'autre :?

Et aussi:

Essentiellement, vous créez une fonction qui s'appelle deux fois à chaque appel et n'a aucun moyen de se terminer.

Comment est-ce exactement exécuté deux fois ? À mon avis, rien n'est transmis au second :tant que le premier :n'a pas terminé son exécution, ce qui ne se terminera jamais.

Dans Cpar exemple,

foo()
{
    foo();
    foo(); // never executed 
}

le second foo()n'est pas exécuté du tout, juste parce que le premier foo()ne se termine jamais.

Je pense que la même logique s'applique à :(){ :|: & };:et

:(){ : & };:

fait le même travail que

:(){ :|: & };:

Veuillez m'aider à comprendre la logique.

Severus Tux
la source
9
La commande dans les pipelines s'exécute en parallèle, dans :|:, le second :n'a pas besoin d'attendre que le premier soit terminé.
cuonglm

Réponses:

26

La tuyauterie ne nécessite pas que la première instance se termine avant que l'autre ne démarre. En fait, tout ce qu'il fait est de rediriger la sortie standard de la première instance vers la sortie standard de la seconde, afin qu'elles puissent fonctionner simultanément (comme elles doivent le faire pour que la bombe fork fonctionne).

Eh bien, quelle est exactement la sortie de :? qu'est-ce qui se passe à l'autre :?

':' n'écrit rien à l'autre ':' instance, il redirige simplement la sortie standard vers la sortie standard de la deuxième instance. S'il écrit quelque chose pendant son exécution (ce qu'il ne fera jamais, puisqu'il ne fait que se bifurquer), il ira au stdin de l'autre instance.

Cela aide à imaginer stdin et stdout comme un tas:

Tout ce qui est écrit dans le stdin sera empilé pour que le programme décide de le lire, tandis que le stdout fonctionne de la même manière: une pile sur laquelle vous pouvez écrire, afin que d'autres programmes puissent le lire quand ils le souhaitent.

De cette façon, il est facile d'imaginer des situations comme un tuyau sans communication (deux piles vides) ou des écritures et des lectures non synchronisées.

Comment est-ce exactement exécuté deux fois? À mon avis, rien n'est transmis au second :tant que le premier :n'a pas terminé son exécution, ce qui ne se terminera jamais.

Comme nous redirigeons simplement l'entrée et la sortie des instances, il n'est pas nécessaire que la première instance se termine avant le démarrage de la seconde. Il est généralement souhaitable que les deux s'exécutent simultanément afin que le second puisse fonctionner avec les données analysées par le premier à la volée. C'est ce qui se passe ici, les deux seront appelés sans avoir à attendre la fin du premier. Cela s'applique à toutes les lignes de commandes des chaînes de tuyaux .

Je pense que la même logique s'applique à: () {: |: &} ;: et

:(){ : & };:

Fait le même travail que

:(){ :|: & };:

La première ne fonctionnerait pas, car même si elle s'exécute de manière récursive, la fonction est appelée en arrière-plan ( : &). Le premier :n'attend pas que «l'enfant» :revienne avant de se terminer, donc à la fin vous n'auriez probablement qu'une seule instance d' :exécution. Si vous l'aviez, :(){ : };:cela fonctionnerait cependant, car le premier :attendrait le retour de "l'enfant" :, qui attendrait le retour de son propre "enfant" :, etc.

Voici à quoi ressembleraient différentes commandes en termes de nombre d'instances en cours d'exécution:

:(){ : & };:

1 instance (appels :et sorties) -> 1 instance (appels :et sorties) -> 1 instance (appels :et sorties) -> 1 instance -> ...

:(){ :|: &};:

1 instance (appelle 2 :et quitte) -> 2 instances (chacune appelle 2 :et quitte) -> 4 instances (chacune appelle 2 :et quitte) -> 8 instances -> ...

:(){ : };:

1 instance (appelle :et attend son retour) -> 2 instances (l'enfant appelle une autre :et attend qu'elle revienne) -> 3 instances (l'enfant appelle une autre :et attend qu'elle revienne) -> 4 instances -> ...

:(){ :|: };:

1 instance (appelle 2 :et attend qu'ils reviennent) -> 3 instances (les enfants appellent 2 :par chacun et attendent qu'ils reviennent) -> 7 instances (les enfants appellent 2 :par chacun et attendent qu'ils reviennent) -> 15 instances -> ...

Comme vous pouvez le voir, appeler la fonction en arrière-plan (en utilisant &) ralentit en fait la bombe fourchette, car l'appelé se fermera avant le retour des fonctions appelées.

IanC
la source
Question. Fonctionnerait :(){ : & && : &}; :également comme une bombe à fourche? Vous augmenteriez également de façon exponentielle et, en fait, vous pourriez en mettre plusieurs : &pour augmenter encore plus rapidement.
JFA
@JFA `─> $: () {: & &&: &}; : `donne une erreur de syntaxe bash: syntax error near unexpected token &&' . Vous pouvez faire ceci:, :(){ $(: &) && $(: &)}; :Mais encore une fois, contrairement au pipeline, cela ne fonctionnera pas en parallèle. Ce qui serait équivalent à :(){: & };:. Vous souhaitez vérifier? essayez ceci time $( $(sleep 1 & ) && $(sleep 1 &) )et celatime $(sleep 1 | sleep 1)
Severus Tux
Exactement, le :(){ $(: &) && $(: &)};est une fonction qui émet une opération logique ET dans les valeurs de retour des première et deuxième instances. Le problème est, puisqu'un ET logique n'est vrai que si les deux valeurs sont vraies, pour des raisons d'efficacité, seule la première instance sera exécutée. Si sa valeur de retour est 1, la deuxième instance s'exécutera. Si vous voulez rendre la bombe à fourche encore plus rapide, je pense que vous pouvez simplement injecter plus d'instances dans la chaîne, comme:(){ :|:|: &}; :
IanC
Juste comme note latérale, de nombreux scripts utilisent ce comportement de AND dans la situation suivante: Supposons que vous vouliez exécuter prog2 si prog1 renvoie true (qui dans bash est 0). Au lieu de faire une instruction if ( if [ prog1 ]; then; prog2; fi), vous pouvez simplement écrire ( prog1 && prog2), et prog2 ne s'exécutera que si la valeur de retour de prog1 est vraie.
IanC
Ok, ce sont tous d'excellents points. J'utilise &&pour appeler apt-get update && apt-get upgrade, et &à la fin de la ligne pour exécuter en arrière-plan, mais c'est un grand point qu'ils ne fonctionneront pas ensemble. Un point-virgule ne fonctionne pas non plus avec l'esperluette.
JFA