Dlaczego użycie „tak” na potokach bash * nie * powoduje nieskończonych pętli?

16

Zgodnie z jego dokumentacją, bash czeka, aż wszystkie polecenia w potoku zakończą działanie, zanim przejdzie dalej

Powłoka czeka na zakończenie wszystkich poleceń w potoku przed zwróceniem wartości.

Dlaczego więc polecenie yes | truekończy się natychmiast? Czy yespętla nie powinna być wieczna i spowodować, że rurociąg nigdy nie powróci?


I podpytanie: zgodnie ze specyfikacją POSIX potoki powłoki mogą powrócić po zakończeniu ostatniego polecenia lub poczekać, aż wszystkie polecenia zakończą się. Czy zwykłe muszle zachowują się inaczej w tym sensie? Czy są jakieś muszle, w których yes | truezapętla się na zawsze?

hugomg
źródło
yes | tee >(true) >/dev/nullzrobi tak, jak się spodziewasz, tak teedalej, dopóki wszyscy pisarze nie umrą, więc trueodejście nie zakłóci ich całkowicie.
Charles Duffy,
1
truejest w zasadzie {return 0;}programem, więc nie spodziewałbym się, że będzie działał długo, nie mówiąc już o wieczności.
Dmitrij Grigoriew

Odpowiedzi:

33

Po truewyjściu strona odczytu potoku jest zamknięta, ale yeskontynuuje próbę zapisu po stronie zapisu. Ten warunek nazywany jest „zepsutą rurą” i powoduje, że jądro wysyła SIGPIPEsygnał do yes. Ponieważ yesnic specjalnego w tym sygnale nie zostanie zabite, zostanie zabity. Jeśli zignoruje sygnał, jego writewywołanie zakończy się niepowodzeniem z kodem błędu EPIPE. Programy, które to robią, muszą być przygotowane do zauważenia EPIPEi zaprzestania pisania, w przeciwnym razie przejdą w nieskończoną pętlę.

Jeśli zrobisz strace yes | true1 , zobaczysz, że jądro przygotowuje się do obu możliwości:

write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 4096) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=17556, si_uid=1000} ---
+++ killed by SIGPIPE +++

straceobserwuje zdarzenia za pośrednictwem interfejsu API debugera, który najpierw informuje go o powrocie wywołania systemowego z błędem, a następnie o sygnale. Z yes„s perspektywy, choć zdarza się pierwszy sygnał. (Technicznie sygnał jest dostarczany po tym, jak jądro zwróci kontrolę w przestrzeni użytkownika, ale przed wykonaniem kolejnych instrukcji maszyny, więc funkcja write„otoki” w bibliotece C nie ma szans na ustawienie errnoi powrót do aplikacji.)


1 Niestety, stracejest specyficzny dla Linuksa. Większość współczesnych Uniksów ma jakieś polecenie, które robi coś podobnego, ale często ma inną nazwę, prawdopodobnie nie dekoduje argumentów syscall tak dokładnie, a czasami działa tylko dla roota.

Casey
źródło
3
@hugomg w takim przypadku rura jest całkowicie nieistotna.
muru
3
@hugomg, ponieważ nic nie yesjest podłączone do potoku.
muru
4
To prawda, że ​​jest to demonstracja udokumentowanego zachowania „poczekaj, aż wszystkie polecenia zakończą się przed zakończeniem potoku”. To po prostu uniemożliwia yesuzyskanie SIGPIPE, ponieważ FD, do którego pisze, nie jest podłączony do potoku.
Tom Hunt
2
@hugomg, zapętla się na zawsze w ten sam sposób, co zapętla się na yes >/dev/nullzawsze. Nic nie pokazuje w potokach, co nie jest prawdą również w przypadku prostych poleceń (jak wskazuje zachowanie oczekiwania na zakończenie, również w przypadku prostych poleceń).
Charles Duffy,
2
@zwol: Myślę, że używamy tutaj naszych terminów z nieco innymi znaczeniami lub myślimy o rzeczach z nieco innych perspektyw… ale w obu przypadkach write()(funkcja w libc) nie powraca (przekazuje kontrolę do komputera po nim) aż do procedura obsługi sygnałów została uruchomiona, ale ponieważ procedura obsługi sygnału kończy program, sterowanie nigdy nie jest przekazywane, a zatem write()nigdy nie powraca. Tak, jest to zaimplementowane w jądrze przez xxx_write()powrót funkcji -EPIPE, ale debugujemy program przestrzeni użytkownika i nie jesteśmy tym zainteresowani.
Dietrich Epp
5

Czy są jakieś muszle, w których tak | prawda zapętli się na zawsze?

Mało prawdopodobne, ponieważ yespolecenie korzysta z potoku i zakończy się niepowodzeniem po zerwaniu potoku. sleepz drugiej strony nie używa potoku, więc:

sleep 100000000 | true

będzie działać przez co najmniej 100000000 sekund.

muru
źródło
2
Uważaj na wszystkie nowoczesne powłoki, które nie rozwidlają się dla ostatniego (najbardziej po prawej) wbudowanego polecenia w potoku i gdzie truejest wbudowane. Odnosi się to do ostatnich wersjach Bourne Shell, ksh93, zsh. Jeśli naciśniesz, ^Zgdy takie polecenie jest uruchomione, spowoduje to zawieszenie uśpienia, a powłoka nigdy nie będzie mogła się zregenerować bez pomocy z zewnątrz.
szczęśliwie
3
zsh 4.3.4 (i386-pc-solaris2.11) tutaj, więc wygląda na to, że ostatnio został zmodyfikowany. Ciekawy pomysł, muszę sprawdzić, czy mogę wdrożyć podobną poprawkę dla powłoki Bourne'a. Nadal jest pytanie, jak to działa i która grupa procesów tty jest używana, tak jak w powłoce Bourne'a. Fakt, że najwyższą komendą jest wbudowane polecenie, odkryto po tym, jak grupa procesów dla snu została już skonfigurowana na zawsze.
szczęśliwie
2
@CharlesDuffy z tego, co rozumiem, schily utrzymuje wersję sh, do której wspomina ulepszenia współczesnych powłok. Gdzieś o tym napisał.
muru
3
Powłoka Bourne'a w archiwum pamiątkowym była utrzymywana do ~ 2007 r., Ale nigdy nie została w pełni przenośna, ponieważ nadal zawiera połączenia z sbrk(). Przenośna i utrzymana wersja znajduje się w pakiecie narzędzi schily, a @Charles Duffy już odkrył lokalizację dla informacji ;-)
schily
2
@muru wiele funkcji, które bshprzeniosłem z powrotem do Bourne Shell, pochodzi z mojego systemu (Berthold Shell z VBERTOS, wersja UNOS z ulepszoną pamięcią wirtualną - pierwszy klon UNIX). Bsh dostał wiele funkcji csh w 1984 i 1985 roku, ale mechanizm aliasów z UNOS był lepszy niż ten z csh już w 1980 roku. Inne nowe funkcje Bourne Shell pochodzą z POSIX, aby umożliwić osiągnięcie zgodności z POSIX.
szczęśliwie