A tubulação não exige que a primeira instância termine antes da outra iniciar. Na verdade, tudo o que está realmente fazendo é redirecionar o stdout da primeira instância para o stdin da segunda, para que eles possam estar executando simultaneamente (como é necessário para o fork bomb funcionar).
Bem, qual é exatamente a saída :? o que está sendo passado para o outro :?
':' não está escrevendo nada para a outra instância ':', apenas redirecionando o stdout para o stdin da segunda instância. Se ele escreve alguma coisa durante a sua execução (que nunca, uma vez que não faz nada, mas se bifurcar em si) que iria para o stdin da outra instância.
Ajuda a imaginar stdin e stdout como uma pilha:
Tudo o que está escrito no stdin será empilhado pronto para quando o programa decidir ler a partir dele, enquanto o stdout funciona da mesma maneira: uma pilha na qual você pode escrever, para que outros programas possam ler quando quiserem.
Dessa forma, é fácil imaginar situações como um canal sem comunicação (duas pilhas vazias) ou gravações e leituras não sincronizadas.
Como exatamente isso é executado duas vezes? Na minha opinião, nada é passado para o segundo :até que o primeiro :termine sua execução, o que realmente nunca terminará.
Como estamos apenas redirecionando a entrada e a saída das instâncias, não é necessário que a primeira instância termine antes do início da segunda. Na verdade, geralmente é desejável que ambos sejam executados simultaneamente para que o segundo possa trabalhar com os dados analisados pelo primeiro em tempo real. É o que acontece aqui, ambos serão chamados sem precisar esperar o primeiro final. Isso se aplica a todas as linhas de comandos de cadeias de tubulação .
Eu estou pensando que a mesma lógica se aplica a: () {: |: &} ;: e
:(){ : & };:
Faz o mesmo trabalho que
:(){ :|: & };:
O primeiro não funcionaria, porque, embora esteja executando recursivamente, a função está sendo chamada em segundo plano ( : &). O primeiro :não espera até que o "filho" :retorne antes de terminar, então no final você provavelmente terá apenas uma instância de :execução. Se você tivesse :(){ : };:, funcionaria, pois o primeiro :aguardaria o :retorno do "filho" , o que esperaria o retorno do seu "filho" :, e assim por diante.
Veja como seriam os comandos diferentes em termos de quantas instâncias seriam executadas:
:(){ : & };:
1 instância (chamadas :e saídas) -> 1 instância (chamadas :e saídas) -> 1 instância (chamadas :e saídas) -> 1 instância -> ...
:(){ :|: &};:
1 instância (chama 2 :'s e encerra) -> 2 instâncias (cada uma chama 2 :' e encerra) -> 4 instâncias (cada uma chama 2 :'e encerra) -> 8 instâncias -> ...
:(){ : };:
1 instância (chama :e aguarda o retorno) -> 2 instâncias (o filho chama outro :e espera o retorno) -> 3 instâncias (o filho chama outro :e espera o retorno) -> 4 instâncias -> ...
:(){ :|: };:
1 instância (chama 2 :e espera que eles retornem) -> 3 instâncias (filhos chama 2 :e cada um e espera que eles retornem) -> 7 instâncias (filhos chama 2 :e cada e espera que eles retornem) -> 15 instâncias -> ...
Como você pode ver, chamar a função em segundo plano (usando &) na verdade reduz a velocidade da bifurcação, porque o chamado termina antes que as funções chamadas retornem.
:|:segundo,:não é necessário esperar o primeiro ser concluído.