Как именно типичная «бомба» снаряда называет себя дважды?

15

Пройдя через знаменитые вопросы о Fork Bomb на Askubuntu и многих других сайтах Stack Exchange, я не совсем понимаю, что все говорят, как будто это очевидно.

Многие ответы ( лучший пример ) говорят так:

« {:|: &}означает запустить функцию :и :снова отправить ее вывод в функцию»

Ну, что именно на выходе :? Что передается другому :?

А также:

По сути, вы создаете функцию, которая вызывает себя дважды при каждом вызове и не имеет возможности завершить себя.

Как именно это выполняется дважды ? По моему мнению, ничто не передается второму :до тех пор, пока первое :не завершит его выполнение, которое фактически никогда не закончится.

В C, например,

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

второе foo()вообще не выполняется, просто потому, что первое foo()никогда не заканчивается.

Я думаю, что та же логика применима :(){ :|: & };:и к

:(){ : & };:

выполняет ту же работу, что и

:(){ :|: & };:

Пожалуйста, помогите мне понять логику.

Северус Тукс
источник
9
Команды в конвейерах выполняются параллельно, во :|:-вторых :, не нужно ждать завершения первого.
cuonglm

Ответы:

26

Трубопровод не требует, чтобы первый экземпляр заканчивался до запуска другого. На самом деле, все это на самом деле делает это перенаправляет стандартный вывод первого экземпляра в стандартный ввод из второй, так что они могут работать одновременно ( так как они должны для вилочного бомбы на работу).

Ну, что именно на выходе :? что передается другому :?

':' ничего не записывает в другой экземпляр ':', он просто перенаправляет стандартный вывод на стандартный ввод второго экземпляра. Если он что-то пишет во время своего выполнения (чего он никогда не сделает, так как он ничего не делает, кроме как разветвляется), он перейдет к стандартному входу другого экземпляра.

Это помогает представить stdin и stdout в виде кучи:

Все, что записано в stdin, будет готово, когда программа решит читать с него, в то время как stdout работает так же: куча, в которую вы можете писать, поэтому другие программы могут читать с нее, когда захотят.

Таким образом, легко представить себе ситуации, такие как канал, который не имеет связи (две пустые группы) или несинхронизированные операции записи и чтения.

Как именно это выполняется дважды? По моему мнению, ничто не передается второму :до тех пор, пока первое :не завершит его выполнение, которое фактически никогда не закончится.

Так как мы просто перенаправляем ввод и вывод экземпляров, не требуется, чтобы первый экземпляр заканчивался до запуска второго. Обычно желательно, чтобы оба запускались одновременно, чтобы второй мог работать с данными, которые анализируются первым на лету. Вот что происходит здесь, оба будут вызваны без необходимости ждать, пока первый закончит. Это относится ко всем цепочкам командных линий.

Я думаю, что та же логика применима к: () {: |: &} ;: и

:(){ : & };:

Делает ту же работу, что и

:(){ :|: & };:

Первый из них не будет работать, потому что, хотя он работает сам по себе рекурсивно, функция вызывается в фоновом режиме ( : &). Первый :не ждет, пока «потомок» :вернется, прежде чем завершится сам, так что в итоге у вас, вероятно, будет только один экземпляр :выполнения. Если бы у вас было :(){ : };:это, это сработало бы, так как первый :будет ждать :возвращения «ребенка» , который будет ждать возвращения своего «ребенка» :, и так далее.

Вот как будут выглядеть разные команды с точки зрения количества запущенных экземпляров:

:(){ : & };:

1 экземпляр (вызовы :и выходы) -> 1 экземпляр (вызовы :и выходы) -> 1 экземпляр (вызовы :и выходы) -> 1 экземпляр -> ...

:(){ :|: &};:

1 экземпляр (вызов 2 :и выход) -> 2 экземпляра (каждый вызов 2 :и выход) -> 4 экземпляра (каждый вызов 2 :и выход) -> 8 экземпляров -> ...

:(){ : };:

1 экземпляр (вызывает :и ждет его возврата) -> 2 экземпляра (дочерний вызов другого :и ждет его возврата) -> 3 экземпляра (дочерний вызов другого :и ожидает его возврата) -> 4 экземпляра -> ...

:(){ :|: };:

1 экземпляр (вызывает 2 :и ждет их возврата) -> 3 экземпляра (дети вызывают 2 :и ждут, пока они вернутся) -> 7 экземпляров (дети вызывают 2 :и ждут, пока они вернутся) -> 15 экземпляров -> ...

Как вы можете видеть, вызов функции в фоновом режиме (с использованием &) на самом деле замедляет форк-бомбу, потому что вызываемый будет выходить до того, как вызванные функции вернутся.

IanC
источник
Вопрос. Будет :(){ : & && : &}; :также работать как вилка бомба? Вы также увеличите экспоненциально, и, на самом деле, вы можете добавить : &туда несколько, чтобы увеличить их еще быстрее.
JFA
@JFA `─> $: () {: & &&: &}; : `дает синтаксическую ошибку bash: syntax error near unexpected token &&' . Вы можете сделать это: :(){ $(: &) && $(: &)}; :Но, опять же, в отличие от конвейера, он не будет работать параллельно. Что будет эквивалентно :(){: & };:. Как проверить? попробуйте это time $( $(sleep 1 & ) && $(sleep 1 &) )и этоtime $(sleep 1 | sleep 1)
Северус Тукс
Точно, :(){ $(: &) && $(: &)};это функция, которая выдает логическую операцию И в возвращаемых значениях первого и второго экземпляра. Проблема в том, что, поскольку логическое И истинно, только если оба значения истинны, для эффективности будет запущен только первый экземпляр. Если его возвращаемое значение равно 1, то запускается второй экземпляр. Если вы хотите сделать вилочную бомбу еще быстрее, я думаю, вы могли бы просто :(){ :|:|: &}; :
направить
Как примечание, многие сценарии используют это поведение AND в следующей ситуации: скажем, вы хотите запустить prog2, если prog1 возвращает true (значение bash равно 0). Вместо выполнения оператора if ( if [ prog1 ]; then; prog2; fi) вы можете просто написать ( prog1 && prog2), и prog2 будет работать только в том случае, если возвращаемое значение prog1 было истинным.
IanC
Хорошо, это все замечательные моменты. Я использую &&для вызова apt-get update && apt-get upgrade, и &в конце строки, чтобы работать в фоновом режиме, но это здорово, что они не будут работать вместе. Точка с запятой также не работает с амперсандом.
JFA