典型的shell“叉子炸弹”究竟是如何调用自己两次的?

Sev*_*Tux 16 command-line bash fork

在浏览了Askubuntu 和许多其他 Stack Exchange 站点上著名的Fork Bomb 问题之后,我不太明白每个人在说什么,因为这很明显。

许多答案(最佳示例)是这样说的:

"{:|: &}表示运行该函数:并将其输出:再次发送给该函数 "

那么,输出究竟什么:?传递给对方的是:什么?

并且:

本质上,您正在创建一个函数,该函数每次调用都会调用两次,并且没有任何方法可以终止自身。

究竟是如何执行两次的?在我看来,:在第一个:完成执行之前,不会将任何内容传递给第二个,这实际上永远不会结束。

C例如,

foo()
{
    foo();
    foo(); // never executed 
}
Run Code Online (Sandbox Code Playgroud)

第二个foo()根本没有执行,只是因为第一个foo()永远不会结束。

我认为同样的逻辑适用于:(){ :|: & };:

:(){ : & };:
Run Code Online (Sandbox Code Playgroud)

做同样的工作

:(){ :|: & };:
Run Code Online (Sandbox Code Playgroud)

请帮助我理解逻辑。

Ian*_*anC 26

管道不要求第一个实例在另一个实例开始之前完成。实际上,它真正要做的就是将第一个实例的stdout重定向到第二个实例的stdin,因此它们可以同时运行(因为它们必须使 fork 炸弹工作)。

那么,究竟是什么输出:?传递给对方的是:什么?

':' 没有向另一个 ':' 实例写入任何内容,它只是将标准输出重定向到第二个实例的标准输入如果它在执行期间写了一些东西(它永远不会,因为它除了分叉自己什么都不做)它会转到另一个实例的标准输入

stdinstdout想象成一堆是有帮助的:

写入stdin 的任何内容都会在程序决定从中读取时堆积起来,而stdout 的工作方式相同:您可以写入一堆,以便其他程序可以在需要时从中读取。

这样很容易想象这样的情况,比如没有发生通信的管道(两个空堆)或非同步写入和读取。

究竟是如何执行两次的?在我看来,:在第一个:完成执行之前,不会将任何内容传递给第二个,这实际上永远不会结束。

由于我们只是重定向实例的输入和输出,因此不需要在第二个实例开始之前完成第一个实例。实际上通常希望两者同时运行,以便第二个可以动态处理第一个正在解析的数据。这就是这里发生的事情,两者都将被调用而无需等待第一个完成。这适用于所有管道链命令行。

我认为同样的逻辑适用于 :(){ :|: & };: 和

:(){ : & };:
Run Code Online (Sandbox Code Playgroud)

做同样的工作

:(){ :|: & };:
Run Code Online (Sandbox Code Playgroud)

第一个不起作用,因为即使它自己递归运行,该函数也在后台调用 ( : &)。第一个:不会等到“孩子”:返回才结束自己,所以最后你可能只有一个:运行实例。如果你有:(){ : };:它会工作,因为第一个:会等待“孩子”:返回,它会等待自己的“孩子”:返回,依此类推。

以下是不同命令在运行的实例数量方面的外观:

:(){ : & };:

1 个实例(调用:和退出)-> 1 个实例(调用:和退出)-> 1 个实例(调用:和退出)-> 1 个实例-> ...

:(){ :|: &};:

1 个实例(调用 2:并退出)-> 2 个实例(每个调用 2:并退出)-> 4 个实例(每个调用 2:并退出)-> 8 个实例-> ...

:(){ : };:

1 个实例(调用:并等待它返回)-> 2 个实例(子调用另一个实例:并等待它返回)-> 3 个实例(子调用另一个实例:并等待它返回)-> 4 个实例-> ...

:(){ :|: };:

1 个实例(调用 2:并等待他们返回)-> 3 个实例(孩子们:每个调用 2并等待他们返回)-> 7 个实例(孩子们:每个调用 2并等待他们返回) -> 15 个实例 -> ...

如您所见,在后台调用该函数(使用&)实际上会减慢 fork 炸弹的速度,因为被调用者将在被调用函数返回之前退出。