¿Cómo se llama a sí mismo la típica "bomba tenedor" de conchas dos veces?

15

Después de pasar por las famosas preguntas de Fork Bomb en Askubuntu y muchos otros sitios de Stack Exchange, no entiendo lo que todos dicen como si fuera obvio.

Muchas respuestas (el mejor ejemplo ) dicen esto:

" {:|: &}significa ejecutar la función :y enviar su salida a la :función nuevamente"

Bueno, ¿cuál es exactamente la salida de :? ¿Qué se pasa al otro :?

Y también:

Básicamente, está creando una función que se llama a sí misma dos veces en cada llamada y no tiene ninguna forma de terminar.

¿Cómo se ejecuta exactamente eso dos veces ? En mi opinión, no se pasa nada al segundo :hasta que el primero :termina su ejecución, que en realidad nunca terminará.

En Cpor ejemplo,

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

el segundo foo()no se ejecuta en absoluto, solo porque el primero foo()nunca termina.

Estoy pensando que la misma lógica se aplica a :(){ :|: & };:y

:(){ : & };:

hace el mismo trabajo que

:(){ :|: & };:

Por favor, ayúdame a entender la lógica.

Severus Tux
fuente
99
El comando en las tuberías se ejecuta en paralelo, en :|:, el segundo :no necesita esperar al primero completado.
Cuonglm

Respuestas:

26

Las tuberías no requieren que la primera instancia finalice antes de que comience la otra. En realidad, todo lo que realmente está haciendo es redirigir el stdout de la primera instancia al stdin de la segunda, para que puedan ejecutarse simultáneamente (como tienen que hacer para que funcione la bomba tenedor).

Bueno, ¿cuál es exactamente la salida de : ? ¿Qué se pasa al otro :?

':' no está escribiendo nada a la otra instancia ':', solo está redirigiendo el stdout al stdin de la segunda instancia. Si escribe algo durante su ejecución (que nunca lo hará, ya que no hace nada más que bifurcarse), iría al stdin de la otra instancia.

Ayuda a imaginar stdin y stdout como una pila:

Todo lo que esté escrito en el stdin estará listo para cuando el programa decida leerlo, mientras que el stdout funciona de la misma manera: un montón en el que puedes escribir, para que otros programas puedan leerlo cuando lo deseen.

De esa manera, es fácil imaginar situaciones como una tubería que no tiene comunicación (dos pilas vacías) o escrituras y lecturas no sincronizadas.

¿Cómo se ejecuta exactamente eso dos veces? En mi opinión, no se pasa nada al segundo :hasta que el primero :termina su ejecución, que en realidad nunca terminará.

Como solo estamos redirigiendo la entrada y la salida de las instancias, no es necesario que la primera instancia finalice antes de que comience la segunda. En realidad, generalmente se desea que ambos se ejecuten simultáneamente para que el segundo pueda trabajar con los datos que analiza el primero sobre la marcha. Eso es lo que sucede aquí, ambos serán llamados sin necesidad de esperar a que termine el primero. Eso se aplica a todas las líneas de comandos de cadenas de tuberías.

Estoy pensando que la misma lógica se aplica a: () {: |: &} ;: y

:(){ : & };:

Hace el mismo trabajo que

:(){ :|: & };:

El primero no funcionaría, porque a pesar de que se ejecuta de forma recursiva, la función se llama en segundo plano ( : &). El primero :no espera hasta que el "hijo" :regrese antes de terminar, por lo que al final probablemente solo tenga una instancia de :ejecución. Si usted tuviera :(){ : };:que funcionaría sin embargo, ya que la primera :sería esperar a que el "niño": para volver, lo que esperar a que su propio "niño" :para volver, y así sucesivamente.

Así es como se verían los diferentes comandos en términos de cuántas instancias se estarían ejecutando:

:(){ : & };:

1 instancia (llamadas :y salidas) -> 1 instancia (llamadas :y salidas) -> 1 instancia (llamadas :y salidas) -> 1 instancia -> ...

:(){ :|: &};:

1 instancia (llama a 2 :y se cierra) -> 2 instancias (cada una llama a 2 :y se cierra) -> 4 instancias (cada una llama a 2 :y se cierra) -> 8 instancias -> ...

:(){ : };:

1 instancia (llama :y espera a que regrese) -> 2 instancias (el niño llama a otro :y espera a que regrese) -> 3 instancias (el niño llama a otro :y espera a que regrese) -> 4 instancias -> ...

:(){ :|: };:

1 instancia (llama a 2 :'s y espera a que regresen) -> 3 instancias (los niños llaman a 2 :' cada uno y espera que regresen) -> 7 instancias (los niños llaman a 2 :'' cada uno y espera a que regresen) -> 15 instancias -> ...

Como puede ver, llamar a la función en segundo plano (usando &) en realidad ralentiza la bomba tenedor, porque la persona que llama se cerrará antes de que regresen las funciones llamadas.

IanC
fuente
Pregunta. ¿Funcionaría :(){ : & && : &}; :también como una bomba tenedor? También aumentaría exponencialmente y, de hecho, podría poner múltiples : &'s allí para aumentarlo aún más rápido.
JFA
@JFA `─> $: () {: & &&: &}; : `da un error de sintaxis bash: syntax error near unexpected token &&' . Podría hacer esto: :(){ $(: &) && $(: &)}; :Pero, de nuevo, a diferencia de la tubería, eso no se ejecutará en paralelo. Cuál sería equivalente a :(){: & };:. ¿Quieres verificar? prueba esto time $( $(sleep 1 & ) && $(sleep 1 &) )y estotime $(sleep 1 | sleep 1)
Severus Tux
Exactamente, :(){ $(: &) && $(: &)};es una función que emite una operación AND lógica en los valores de retorno de la primera y segunda instancia. El problema es que, dado que un AND lógico solo es verdadero si ambos valores son verdaderos, por eficiencia solo se ejecutará la primera instancia. Si su valor de retorno es 1, se ejecutará la segunda instancia. Si quieres hacer que la bomba tenedor sea aún más rápida, creo que podrías :(){ :|:|: &}; :
conectar
Como nota al margen, muchos scripts usan este comportamiento de AND en la siguiente situación: Digamos que desea ejecutar prog2 si prog1 devuelve verdadero (que en bash es 0). En lugar de hacer una declaración if ( if [ prog1 ]; then; prog2; fi), podría escribir ( prog1 && prog2), y prog2 solo se ejecutaría si el valor de retorno de prog1 fuera verdadero.
IanC
Ok, estos son todos grandes puntos. Solía &&llamar apt-get update && apt-get upgrade, y &al final de la línea para ejecutar en segundo plano, pero ese es un gran punto de que no funcionarán juntos. Un punto y coma tampoco funciona con el ampersand.
JFA