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?


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.


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.


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.