Rury, jak przepływają dane w potoku?

22

Nie rozumiem, jak przepływają dane w rurociągu, i mam nadzieję, że ktoś może wyjaśnić, co się tam dzieje.

Myślałem, że potok poleceń przetwarza pliki (tekst, tablice ciągów) wiersz po wierszu. (Jeśli każde polecenie działa osobno wiersz po wierszu.) Każdy wiersz tekstu przechodzi przez potok, polecenia nie czekają, aż poprzednie zakończy przetwarzanie całego tekstu.

Ale wydaje się, że tak nie jest.

Oto przykładowy test. Jest kilka wierszy tekstu. Wielkie litery i powtarzam każdą linię dwa razy. Robię to z cat text | tr '[:lower:]' '[:upper:]' | sed 'p'.

Aby śledzić proces, możemy go uruchomić „interaktywnie” - pomiń wejściową nazwę pliku cat. Każda część rurociągu przebiega linia po linii:

$ cat | tr '[:lower:]' '[:upper:]'
alkjsd
ALKJSD
sdkj
SDKJ
$ cat | sed 'p'
line1
line1
line1
line 2
line 2
line 2

Ale cały potok czeka, aż skończę dane wejściowe EOFi dopiero wtedy wypisuje wynik:

$ cat | tr '[:lower:]' '[:upper:]' | sed 'p'
I am writing...
keep writing...
now ctrl-D
I AM WRITING...
I AM WRITING...
KEEP WRITING...
KEEP WRITING...
NOW CTRL-D
NOW CTRL-D

Czy tak ma być? Dlaczego nie jest wiersz po wierszu?

xealits
źródło
To nie jest rura, catbuforuje się aż do zamknięcia standardowego wejścia.
goldilocks
ale tri sedwykonaj linie przetwarzania catsprzed zamknięcia stdin
xealits
Domyślnymi ustawieniami używanymi przez stdio (które, jak sądzę, używają wszystkie wspomniane programy), jest to, że stderr nie jest buforowany, a stdout jest buforowany w linii podczas zapisu do terminala i w pełni buforowany w inny sposób (na przykład, jeśli zapisuje do pliku lub potoku) . Niektóre polecenia mają flagi, które mogą zmieniać buforowanie standardowe, ale wygląda na to, że tr nie.
kasperd

Odpowiedzi:

36

Istnieje ogólna reguła buforowania, po której następuje standardowa biblioteka C we / wy ( stdio) używana przez większość programów uniksowych. Jeśli wyjście trafia do terminala, jest opróżniane na końcu każdej linii; w przeciwnym razie jest opróżniany tylko wtedy, gdy bufor (8 KB na moim systemie Linux / amd64; może być inny na twoim) jest pełny.

Jeśli wszystkie media były zgodnie z ogólną regułę, że widzisz wyjścia z opóźnieniem wszystkich przykładów ( cat|sed, cat|tr, i cat|tr|sed). Ale jest wyjątek: GNU catnigdy nie buforuje swoich danych wyjściowych. Nie używa stdiolub zmienia domyślną stdiozasadę buforowania.

Mogę być całkiem pewien, że używasz GNU, cata nie jakiegoś innego Uniksa, catponieważ inni nie zachowaliby się w ten sposób. Tradycyjny unix catma -uopcję żądania niebuforowanych danych wyjściowych. GNU catignoruje tę -uopcję, ponieważ jej dane wyjściowe są zawsze niebuforowane.

Tak więc, gdy masz rurkę z catlewym, w systemie GNU, przepływ danych przez rurę nie będzie opóźniony. catNawet nie będzie wiersz po wierszu - terminal robi to. Podczas wpisywania danych wejściowych dla cat, twój terminal jest w trybie „kanonicznym” - oparty na linii, z klawiszami edycji takimi jak backspace i ctrl-U, oferującymi możliwość edycji linii, którą wpisałeś przed wysłaniem Enter.

W tym cat|tr|sedprzykładzie trnadal odbiera dane, catgdy tylko naciśniesz Enter, ale trpostępuje zgodnie z stdiodomyślną zasadą: jego wyjście przechodzi do potoku, więc nie opróżnia się po każdej linii. Zapisuje do drugiego potoku, gdy bufor jest pełny lub po otrzymaniu EOF, w zależności od tego, co nastąpi wcześniej.

sedpostępuje również zgodnie z stdiodomyślną polityką, ale jego dane wyjściowe trafiają do terminala, więc zapisze każdą linię, gdy tylko się z nią skończy. Wpływa to na to, ile musisz wpisać, zanim coś pojawi się na drugim końcu potoku - jeśli sedbuforowanie blokowe jego danych wyjściowych, musisz wpisać dwa razy więcej (aby wypełnić trbufor wyjściowy i sed dane wyjściowe bufor).

GNU sedma -uopcję, więc jeśli odwrócisz kolejność i cat|sed -u|trużyjesz, zobaczysz, że dane wyjściowe pojawią się ponownie. (Ta sed -uopcja może być dostępna gdzie indziej, ale nie sądzę, że jest to starożytna tradycja uniksowa taka jak cat -u). O ile wiem, nie ma równoważnej opcji tr.

Istnieje narzędzie o nazwie, stdbufktóre pozwala zmienić tryb buforowania każdego polecenia, które korzysta z stdiowartości domyślnych. Jest to trochę kruche, ponieważ wykorzystuje się LD_PRELOADdo osiągnięcia czegoś, co biblioteka C nie została zaprojektowana do obsługi, ale w tym przypadku wydaje się działać:

cat | stdbuf -o 0 tr '[:lower:]' '[:upper:]' | sed 'p'

źródło
1
dzięki! Świetna odpowiedź. Prawdopodobnie powinienem w jakiś sposób wspomnieć o buforowaniu w pytaniu, aby można je było znaleźć.
xealits
teea ddtakże zwykle grać według własnych zasad. Po połączeniu w wyobraźni trzy narzędzia mogą w przenośny sposób zaprzeczyć jakiejkolwiek potrzebie stdbufw rurociągach w tle.
mikeserv
1
Jest to jeden z powodów, dla których należy unikać bezużytecznego używania kota .
hobbs
8

Właściwie zajęło mi to trochę zrozumienia, a jeszcze więcej odpowiedzi. Świetne pytanie (będę go głosować w następnej kolejności).

Zaniedbałeś próby wypróbowania tr | sedpowyższych elementów do debugowania:

>tr '[:lower:]' '[:upper:]' | sed 'p'
i am writing
still writing
now ctrl-d
I AM WRITING
I AM WRITING
STILL WRITING
STILL WRITING
NOW CTRL-D
NOW CTRL-D
>

Więc najwyraźniej trbuforuje. Naucz się czegoś nowego każdego dnia!

EDYCJA :

Gdy się nad tym zastanawiam, wyodrębniliśmy przyczynę, ale nie dostarczyliśmy wyjaśnienia. Jeśli cat | tr, to pisze od razu, jeśli cat | sed, to pisze od razu, ale jeśli tr | sed, to czeka na EOF. Sugerowałbym, że odpowiedź może być zakopana w kodzie źródłowym trlub sednie, a nie stanowić problem z potokiem.

EDYCJA :

Widzę, że Wumpus dostarczył wyjaśnienie podczas pisania ostatniej edycji. Dzięki!

Poisson Aerohead
źródło
1
rzeczywiście buforują! a test z liniami o wielkości około 8 kb, jak wspomniał Wumpus, pokazuje, że bufor ma rzeczywiście 8 kb. Chciałbym zaakceptować obie odpowiedzi, aby podzielić się reputacją, ale wezmę odpowiedź Wumpusa za bardziej kompletną. W każdym razie dzięki!
xealits
1
Nie ma problemu, moja była odpowiedzią empiryczną, jego była kompetentna.
Poisson Aerohead
Zobacz także to pytanie, które pokazuje, jak używać, stdbufco może być również pomocne. unix.stackexchange.com/questions/182537/...
Joe