Przeplatają się! Próbowałeś tylko krótkich serii wyjściowych, które pozostają nieoświetlone, ale w praktyce trudno jest zagwarantować, że jakikolwiek konkretny wynik pozostaje nieoświetlony.
Buforowanie wyjściowe
To zależy od tego, jak programy buforują swoje dane wyjściowe. Biblioteki stdio , że większość programów używać, kiedy piszesz użyje bufory wyjściowe, aby bardziej wydajne. Zamiast wyprowadzać dane, gdy tylko program wywoła funkcję biblioteki w celu zapisu do pliku, funkcja przechowuje te dane w buforze i faktycznie wysyła dane dopiero po zapełnieniu bufora. Oznacza to, że dane wyjściowe są wykonywane partiami. Dokładniej, istnieją trzy tryby wyjściowe:
- Niebuforowane: dane są zapisywane natychmiast, bez użycia bufora. Może to być powolne, jeśli program zapisuje dane wyjściowe w małych kawałkach, np. Znak po znaku. Jest to domyślny tryb dla standardowego błędu.
- W pełni buforowane: dane są zapisywane tylko wtedy, gdy bufor jest pełny. Jest to tryb domyślny podczas zapisywania do potoku lub zwykłego pliku, z wyjątkiem stderr.
- Buforowany wiersz: dane są zapisywane po każdej nowej linii lub po zapełnieniu bufora. Jest to tryb domyślny podczas pisania do terminala, z wyjątkiem stderr.
Programy mogą przeprogramowywać każdy plik, aby zachowywać się inaczej, i mogą jawnie opróżniać bufor. Bufor jest opróżniany automatycznie, gdy program zamyka plik lub kończy pracę normalnie.
Jeśli wszystkie programy, które piszą do tego samego potoku albo używają trybu buforowanego linii, albo trybu niebuforowanego i zapisują każdą linię pojedynczym wywołaniem funkcji wyjściowej, a jeśli linie są wystarczająco krótkie, aby pisać w jednym fragmencie, wyjście będzie przeplotem całych linii. Ale jeśli jeden z programów korzysta z trybu pełnego buforowania lub jeśli linie są zbyt długie, zobaczysz linie mieszane.
Oto przykład, w którym przeplatam dane wyjściowe z dwóch programów. Użyłem GNU coreutils na Linuksie; różne wersje tych narzędzi mogą zachowywać się inaczej.
yes aaaa
pisze aaaa
wiecznie w tym, co jest zasadniczo równoważne trybowi buforowanemu liniowo. yes
Narzędzie rzeczywiście pisze wiele wierszy na raz, ale za każdym razem emituje wyjście, wyjście jest cała ilość linii.
echo bbbb; done | grep b
pisze bbbb
wiecznie w trybie pełnego buforowania. Wykorzystuje rozmiar bufora 8192, a każda linia ma długość 5 bajtów. Ponieważ 5 nie dzieli 8192, granice między zapisami nie są ogólnie na granicy linii.
Złóżmy je razem.
$ { yes aaaa & while true; do echo bbbb; done | grep b & } | head -n 999999 | grep -e ab -e ba
bbaaaa
bbbbaaaa
baaaa
bbbaaaa
bbaaaa
bbbaaaa
ab
bbbbaaa
Jak widać, tak czasami przerywało grep i vice versa. Tylko około 0,001% linii zostało przerwanych, ale tak się stało. Wyjście jest losowe, więc liczba przerwań będzie się różnić, ale za każdym razem widziałem co najmniej kilka przerw. Gdyby linie były dłuższe, byłby większy ułamek przerwanych linii, ponieważ prawdopodobieństwo przerwania wzrasta wraz ze spadkiem liczby linii na bufor.
Istnieje kilka sposobów dostosowania buforowania wyjściowego . Najważniejsze z nich to:
- Wyłącz buforowanie w programach korzystających z biblioteki stdio bez zmiany jej domyślnych ustawień w programie
stdbuf -o0
znajdującym się w GNU coreutils i niektórych innych systemach, takich jak FreeBSD. Alternatywnie możesz przejść do buforowania linii za pomocą stdbuf -oL
.
- Przełącz na buforowanie linii, kierując wyjście programu przez terminal utworzony właśnie w tym celu za pomocą
unbuffer
. Niektóre programy mogą zachowywać się inaczej na inne sposoby, na przykład grep
domyślnie używa kolorów, jeśli ich wyjściem jest terminal.
- Skonfiguruj program, na przykład przekazując
--line-buffered
do GNU grep.
Zobaczmy ponownie fragment powyżej, tym razem z buforowaniem linii po obu stronach.
{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & } | head -n 999999 | grep -e ab -e ba
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
Więc tym razem tak nigdy nie przeszkadzało grep, ale grep czasami przerywało tak. Zobaczę dlaczego później.
Przeplatanie rur
Tak długo, jak każdy program wyprowadza po jednej linii na raz, a linie są wystarczająco krótkie, linie wyjściowe będą starannie oddzielone. Ale istnieje limit czasu, przez jaki linie mogą działać, aby to zadziałało. Sama rura ma bufor przesyłania. Gdy program wysyła dane do potoku, dane są kopiowane z programu piszącego do bufora przesyłania potoku, a następnie z bufora przesyłania potoku do programu czytającego. (Przynajmniej koncepcyjnie - jądro może czasami zoptymalizować to do pojedynczej kopii.)
Jeśli jest więcej danych do skopiowania niż mieści się w buforze przesyłania potoku, wówczas jądro kopiuje jeden bufor na raz. Jeśli wiele programów pisze do tego samego potoku, a pierwszy program wybrany przez jądro chce napisać więcej niż jeden bufor, to nie ma gwarancji, że jądro ponownie wybierze ten sam program za drugim razem. Na przykład, jeśli P jest rozmiarem bufora, foo
chce zapisać 2 * P bajtów i bar
chce zapisać 3 bajty, wówczas jednym z możliwych przeplotów jest P bajtów z foo
, następnie 3 bajty z bar
i P bajtów z foo
.
Wracając do powyższego przykładu tak + grep, w moim systemie yes aaaa
zdarza się pisać tyle wierszy, ile można zmieścić w buforze 8192 bajtów za jednym razem. Ponieważ do zapisania jest 5 bajtów (4 znaki do wydrukowania i nowa linia), oznacza to, że zapisuje 8190 bajtów za każdym razem. Rozmiar bufora potoku wynosi 4096 bajtów. Możliwe jest zatem pobranie 4096 bajtów z yes, następnie część danych wyjściowych z grep, a następnie reszta zapisu z yes (8190 - 4096 = 4094 bajtów). 4096 bajtów pozostawia miejsce na 819 linii zi aaaa
samotny a
. Stąd linia z tym samotnym, a
po której następuje jedno pismo od grep, dające linię z abbbb
.
Jeśli chcesz zobaczyć szczegóły tego, co się dzieje, getconf PIPE_BUF .
powie ci rozmiar bufora potoku w twoim systemie i możesz zobaczyć pełną listę wywołań systemowych wykonanych przez każdy program za pomocą
strace -s9999 -f -o line_buffered.strace sh -c '{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & }' | head -n 999999 | grep -e ab -e ba
Jak zagwarantować czyste przeplatanie linii
Jeśli długości linii są mniejsze niż rozmiar bufora rury, buforowanie linii gwarantuje, że na wyjściu nie będzie linii mieszanej.
Jeśli długości linii mogą być większe, nie można uniknąć arbitralnego mieszania, gdy wiele programów pisze na tym samym potoku. Aby zapewnić separację, musisz zmusić każdy program do zapisu do innej potoku i użyć programu do połączenia linii. Na przykład GNU Parallel robi to domyślnie.