grep nie jest generowany, dopóki EOF nie zostanie przesłany przez cat

19

Biorąc pod uwagę ten minimalny przykład

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; )

wyprowadza LINE 1, a następnie po upływie jednej sekundy, wyjścia LINE 2, jak oczekiwano .


Jeśli to potokujemy grep LINE

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep LINE

zachowanie jest takie samo, jak w poprzednim przypadku, zgodnie z oczekiwaniami .


Jeśli alternatywnie, potokujemy to do cat

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | cat

zachowanie jest ponownie takie samo, jak oczekiwano .


Jeśli jednak potokujemy grep LINE, a następnie do cat,

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep LINE | cat

nie ma wyjścia, dopóki nie minie sekunda, a obie linie natychmiast pojawią się na wyjściu, czego się nie spodziewałem .


Dlaczego tak się dzieje i jak mogę sprawić, aby ostatnia wersja zachowywała się tak samo, jak pierwsze trzy polecenia?

Lisyarus
źródło
catłączy pliki. Co próbujesz zrobić, dołączając do niego cat?
Douglas odbył się
15
@DouglasHeld Po wywołaniu bez argumentów, catpo prostu czyta stdini wysyła do stdout. Oczywiście wymyśliłem to pytanie z wieloma złożonymi rzeczami w miejsce echoi cat, ale okazały się one nieistotne, ponieważ problem pojawia się w znacznie prostszych przykładach.
lisyarus
3
@DouglasHeld: Rurociągi do kota są często przydatne, aby zmusić stdout, aby nie był terminalem. Na przykład jest to łatwy sposób na uzyskanie wielu poleceń, aby nie używać kolorowych wyników.
wchargin
Przysięgam, że to duplikat innego pytania na temat przepełnienia stosu!
iBug
@wchargin bardzo dziękuję, nauczyłeś mnie czegoś nowego o posix, o którym nigdy nie wiedziałem.
Douglas odbył się

Odpowiedzi:

38

Kiedy grepwyjście (przynajmniej GNU) nie jest terminalem, buforuje swoje wyjście, co powoduje zachowanie, które widzisz. Funkcję tę można wyłączyć albo używa GNU grep„s --line-bufferedopcję:

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep --line-buffered LINE | cat

lub stdbufnarzędzie:

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | stdbuf -oL grep LINE | cat

Wyłącz buforowanie w potoku ma więcej na ten temat.

Stephen Kitt
źródło
26

Uproszczone wyjaśnienie

Podobnie jak wiele narzędzi, nie jest to coś osobliwego dla jednego programu, grepzmienia standardowe wyjście między buforowaniem liniowym a buforowaniem pełnym . W pierwszym przypadku biblioteka C buforuje dane wyjściowe w pamięci, dopóki bufor przechowujący te dane nie zostanie wypełniony lub nie zostanie do niego dodany znak przesunięcia wiersza (lub program zakończy się czysto), po czym wywołuje write()zapis zawartości bufora. W tym drugim przypadku tylko bufor w pamięci zapełniający się (lub program kończy się czysto) wyzwala write().

Bardziej szczegółowe wyjaśnienie

To jest dobrze znane, ale nieco błędne wyjaśnienie. W rzeczywistości standardowe wyjście nie jest buforowane liniowo, lecz inteligentnie buforowane w bibliotece GNU C i bibliotece BSD C. Standardowe wyjście jest także zaczerwieniona podczas czytania standardowego wejścia wyczerpuje swój bufor w pamięci (pre-odczytu wejścia) i biblioteka C ma zadzwonić read(), aby pobrać trochę więcej wejście i to czyta początek nowej linii. (Jednym z powodów jest zapobieganie zakleszczeniu, gdy inny program łączy się z oboma końcami filtra i oczekuje, że będzie w stanie działać linia po linii, naprzemiennie między zapisem do filtra a odczytem z niego; jak „koprocesowanie” w GNU awkna przykład.)

Wpływ biblioteki C.

grepi inne narzędzia to robią - lub ściślej mówiąc, biblioteki, których używają, robią to, ponieważ jest to zdefiniowana funkcja programowania w języku C - w oparciu o to, co wykrywają jako standardowe wyjście. Jeśli (i tylko jeśli) nie jest to urządzenie interaktywne, wybierają pełne buforowanie, w przeciwnym razie wybierają inteligentne buforowanie. Potok jest uważany za urządzenie nieinteraktywne, ponieważ definicja bycia urządzeniem interaktywnym, przynajmniej w świecie Unixa i Linuksa, jest w istocie isatty()wywołaniem zwracającym wartość true dla odpowiedniego deskryptora pliku.

Obejścia, aby wyłączyć pełne buforowanie

Niektóre narzędzia, takie jak greptakie idiosynkratyczne, jak --line-bufferedta, zmieniają tę decyzję, która, jak widać, jest źle nazwana. Ale znikomo niewielka część programów filtrujących, których można użyć, faktycznie ma taką opcję.

Mówiąc bardziej ogólnie, można użyć narzędzi, które zagłębiają się w określone elementy wewnętrzne biblioteki C i zmieniają jej proces decyzyjny (które mają problemy z bezpieczeństwem, jeśli program, który ma zostać zmieniony, ma ustawiony UID, a także są specyficzne dla poszczególnych bibliotek C, i faktycznie są specyficzne dla programów napisanych lub ułożonych warstwowo na języku C) lub takie narzędzia ptybandage, które nie zmieniają wewnętrznych elementów programu, ale po prostu wstawiają pseudo-terminal jako standardowe wyjście, aby decyzja była „interaktywna”, aby wpływać na to.

Dalsza lektura

JdeBP
źródło
1
Jeśli fraza „buforowana linia” jest myląca, to tak naprawdę nie jest to wina grep, ale bazowych wywołań biblioteki, setbuf/setvbuf . Nie znam wiarygodnego online odniesienia do standardu C, ale np. Strony podręcznika Linux i FreeBSD wraz z opisem POSIX setvbufnazywają go „buforowanym wierszem”. Nawet stała symboliczna _IOLBF.
ilkkachu
Cóż, teraz nauczyłeś się lepiej. Ta strategia buforowania została opisana w dokumentacji biblioteki GNU C, aczkolwiek krótko. Laurent Bercot jest bardziej szczery w tej sprawie. Też o tym wspomniałem.
JdeBP,
Nie sądziłem, że „Twoje oczekiwania są błędne” były dobrym nagłówkiem tego doskonałego wyjaśnienia buforowania danych wyjściowych. Mam nadzieję, że nie masz nic przeciwko, że go usunąłem i dodałem opisowe nagłówki do każdej części odpowiedzi.
Anthony G - sprawiedliwość dla Moniki
2
@ilkkachu Standard C faktycznie używa „buforowanej linii”. Zgodnie z 7.21.3 Pliki , akapit 3 : „Gdy strumień nie jest buforowany, ... Kiedy strumień jest w pełni buforowany, ... Kiedy strumień jest buforowany w linii, znaki mają być przesyłane do lub ze środowiska hosta jako blokuj po napotkaniu znaku nowej linii.… ”W rzeczywistości standard C używa pięć razy dokładnej frazy„ buforowany wiersz ”. Więc to nie jest błędne określenie.
Andrew Henle,
1
Co więcej, podejście opisane tutaj jako „inteligentne buforowanie”, jak rozumiem, wydaje się być dokładnie tym, co standard C opisuje jako „buforowanie linii”. W szczególności, oprócz opróżniania bufora w znakach nowej linii, „Gdy strumień jest buforowany w linii, znaki mają być przesyłane do lub ze środowiska hosta jako blok, gdy [...] wymagane jest wejście w niebuforowanym strumieniu lub gdy wymagane jest wejście w strumieniu buforowanym wierszowo, który wymaga przesłania znaków ze środowiska hosta. ” Więc to nie jest dziwactwo GNU lub BSD, ale raczej to, czego wymaga język.
John Bollinger,
7

Posługiwać się

grep --line-buffered

aby grep nie buforował więcej niż jednej linii na raz.

choroba
źródło