Jak dokładnie typowa „bomba widelcowa” nazywa się dwukrotnie?

15

Po przejściu przez słynne pytania dotyczące Fork Bomb na Askubuntu i wielu innych stronach Stack Exchange, nie do końca rozumiem, co wszyscy mówią, ponieważ to oczywiste.

Wiele odpowiedzi ( najlepszy przykład ) mówi:

{:|: &}oznacza uruchomienie funkcji :i :ponowne przesłanie jej danych wyjściowych do funkcji”

Cóż, co dokładnie jest wynikiem :? Co jest przekazywane drugiemu :?

I również:

Zasadniczo tworzysz funkcję, która wywołuje się dwa razy przy każdym wywołaniu i nie ma możliwości zakończenia się.

Jak dokładnie jest to wykonywane dwukrotnie ? Moim zdaniem nic nie jest przekazywane drugiemu, :dopóki pierwszy nie :zakończy swojego wykonania, co w rzeczywistości nigdy się nie skończy.

Na Cprzykład

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

drugi foo()nie jest wcale wykonywany, tylko dlatego, że pierwszy foo()nigdy się nie kończy.

Myślę, że ta sama logika dotyczy :(){ :|: & };:i

:(){ : & };:

wykonuje tę samą pracę, co

:(){ :|: & };:

Pomóż mi zrozumieć logikę.

Severus Tux
źródło
9
Polecenie w potokach jest uruchamiane równolegle, w :|:drugim :nie trzeba czekać na ukończenie pierwszego.
cuonglm

Odpowiedzi:

26

Rurociągi nie wymagają, aby pierwsza instancja zakończyła się przed uruchomieniem drugiej. W rzeczywistości wszystko, co naprawdę robi, to przekierowanie stdout pierwszej instancji do stdin drugiej instancji , aby mogły one działać jednocześnie (tak jak muszą, aby bomba widełkowa zadziałała).

Co dokładnie jest wynikiem :? co jest przekazywane drugiemu :?

„:” nic nie pisze do drugiej instancji „:”, po prostu przekierowuje stdout do stdin drugiej instancji. Jeśli napisze coś podczas wykonywania (czego nigdy nie zrobi, ponieważ nie robi nic poza rozwidleniem się), przejdzie do normalnego stanu drugiej instancji.

Pomaga wyobrazić sobie stdin i stdout jako stos:

Wszystko, co zostanie zapisane na stdin, zostanie zgromadzone gotowe, gdy program zdecyduje się z niego odczytać, podczas gdy stdout działa w ten sam sposób: stos, na którym można pisać, aby inne programy mogły z niego czytać, kiedy chcą.

W ten sposób łatwo wyobrazić sobie sytuacje takie jak potok, w którym nie ma komunikacji (dwa puste stosy) lub niezsynchronizowane zapisy i odczyty.

Jak dokładnie jest to wykonywane dwukrotnie? Moim zdaniem nic nie jest przekazywane drugiemu, :dopóki pierwszy nie :zakończy swojego wykonania, co w rzeczywistości nigdy się nie skończy.

Ponieważ po prostu przekierowujemy dane wejściowe i wyjściowe instancji, nie ma wymogu, aby pierwsza instancja zakończyła się przed uruchomieniem drugiej. Zazwyczaj pożądane jest, aby oba działały jednocześnie, aby drugi mógł pracować z danymi analizowanymi przez pierwszy w locie. Tak się dzieje tutaj, oba zostaną wywołane bez konieczności oczekiwania na zakończenie pierwszego. Dotyczy to wszystkich linii poleceń łańcuchów rur .

Myślę, że ta sama logika dotyczy: () {: |: &} ;: i

:(){ : & };:

Wykonuje tę samą pracę, co

:(){ :|: & };:

Pierwszy nie zadziałałby, ponieważ chociaż działa on rekurencyjnie, funkcja jest wywoływana w tle ( : &). Pierwszy :nie czeka, aż „dziecko” :powróci, zanim się skończy, więc na koniec prawdopodobnie miałbyś tylko jedną instancję :działania. Gdybyś miał :(){ : };:, zadziałałoby, ponieważ pierwszy :czekałby na :powrót „dziecka” , co czekałoby na :powrót własnego „dziecka” i tak dalej.

Oto, jak wyglądałyby różne polecenia pod względem liczby uruchomionych instancji:

:(){ : & };:

1 instancja (połączenia :i rezygnacje) -> 1 instancja (połączenia :i rezygnacje) -> 1 instancja (połączenia :i rezygnacje) -> 1 instancja -> ...

:(){ :|: &};:

1 wystąpienie (wywołuje 2 :i kończy) -> 2 wystąpienia (każdy wywołuje 2 :i kończy) -> 4 wystąpienia (każdy wywołuje 2 :i kończy) -> 8 wystąpień -> ...

:(){ : };:

1 instancja (dzwoni :i czeka na zwrócenie) -> 2 instancje (dziecko dzwoni do innej :i czeka na zwrot) -> 3 instancje (dziecko dzwoni do innej :i czeka na powrót) -> 4 instancje -> ...

:(){ :|: };:

1 instancja (wywołuje 2 :i czeka, aż powrócą) -> 3 instancje (dzieci dzwonią po 2 :i czekają, aż powrócą) -> 7 instancji (dzieci dzwonią po 2 :i czekają, aż powrócą) -> 15 instancji -> ...

Jak widać, wywołanie funkcji w tle (użycie &) powoduje spowolnienie bomby widelcowej, ponieważ odbiorca zostanie zamknięty, zanim wywołane funkcje powrócą.

IanC
źródło
Pytanie. Czy działałby :(){ : & && : &}; :również jako bomba widełkowa? Zwiększysz także wykładniczo, a tak naprawdę możesz wstawić wielokrotności : &, aby zwiększyć go jeszcze szybciej.
JFA
@JFA `─> $: () {: & &&: &}; : `podaje błąd składniowy bash: syntax error near unexpected token &&' . Możesz to zrobić: :(){ $(: &) && $(: &)}; :Ale znowu, w przeciwieństwie do potoku, nie będzie on działał równolegle. Co byłoby równoważne z :(){: & };:. Lubisz weryfikować? spróbuj tych time $( $(sleep 1 & ) && $(sleep 1 &) )i tychtime $(sleep 1 | sleep 1)
Severus Tux
Dokładnie, :(){ $(: &) && $(: &)};jest to funkcja, która wydaje logiczną operację AND w zwracanych wartościach pierwszej i drugiej instancji. Problem polega na tym, że logiczne AND jest prawdziwe tylko wtedy, gdy obie wartości są prawdziwe, dla wydajności zostanie uruchomiona tylko pierwsza instancja. Jeśli wartością zwracaną jest 1, uruchomi się druga instancja. Jeśli chcesz, aby bomba widelec jeszcze szybciej myślę, że może po prostu rura więcej wystąpień do łańcucha, jak:(){ :|:|: &}; :
IanC
Na marginesie, wiele skryptów używa tego zachowania AND w następującej sytuacji: Powiedzmy, że chcesz uruchomić prog2, jeśli prog1 zwraca true (co w bash jest równe 0). Zamiast wykonywać instrukcję if ( if [ prog1 ]; then; prog2; fi) można po prostu napisać ( prog1 && prog2), a program prog2 będzie działał tylko wtedy, gdy zwracana wartość prog1 będzie prawdziwa.
IanC
Ok, to są wszystkie świetne punkty. Zwykle &&dzwonię apt-get update && apt-get upgrade, a &na końcu linii biegam w tle, ale to świetna rzecz, że nie będą ze sobą współpracować. Średnik również nie działa ze znakiem ampersand.
JFA