Они чередуются! Вы пробовали только короткие пакеты вывода, которые остаются неразделенными, но на практике трудно гарантировать, что какой-либо конкретный вывод останется неразделенным.
Буферизация вывода
Это зависит от того, как программы буферизуют свой вывод. Библиотека stdio, которую большинство программ используют при написании, использует буферы для повышения эффективности вывода. Вместо вывода данных, как только программа вызывает библиотечную функцию для записи в файл, функция сохраняет эти данные в буфере и фактически выводит данные только после заполнения буфера. Это означает, что вывод осуществляется партиями. Точнее, есть три режима вывода:
- Небуферизованный: данные записываются немедленно, без использования буфера. Это может быть медленным, если программа записывает свои выходные данные небольшими частями, например, символ за символом. Это режим по умолчанию для стандартной ошибки.
- Полностью буферизован: данные записываются только тогда, когда буфер заполнен. Это режим по умолчанию при записи в канал или обычный файл, кроме как с помощью stderr.
- Строковая буферизация: данные записываются после каждой новой строки или когда буфер заполнен. Это режим по умолчанию при записи в терминал, кроме как с помощью stderr.
Программы могут перепрограммировать каждый файл, чтобы вести себя по-разному, и могут явно очистить буфер. Буфер очищается автоматически, когда программа закрывает файл или завершает работу в обычном режиме.
Если все программы, которые пишут в один и тот же канал, либо используют режим буферизации строки, либо используют режим без буферизации и записывают каждую строку одним вызовом функции вывода, и если строки достаточно короткие, чтобы записать их в один фрагмент, то вывод будет чередование целых строк. Но если одна из программ использует полностью буферизованный режим или строки слишком длинные, вы увидите смешанные строки.
Вот пример, где я чередую вывод двух программ. Я использовал GNU coreutils в Linux; разные версии этих утилит могут вести себя по-разному.
yes aaaa
пишет aaaa
навсегда в том, что по существу эквивалентно режиму с линейной буферизацией. yes
Утилита на самом деле пишет несколько строк в то время, но каждый раз , когда он испускает выход, выход представляет собой целое число строк.
echo bbbb; done | grep b
пишет bbbb
навсегда в режиме полной буферизации. Он использует размер буфера 8192, и каждая строка имеет длину 5 байт. Поскольку 5 не делит 8192, границы между записями вообще не находятся на границе строк.
Давайте разберем их вместе.
$ { 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
Как видите, да иногда прерывается grep и наоборот. Только около 0,001% линий были прерваны, но это случилось. Выходные данные рандомизированы, поэтому количество прерываний будет меняться, но я видел по крайней мере несколько прерываний каждый раз. Если бы строки были длиннее, была бы более высокая доля прерванных линий, поскольку вероятность прерывания увеличивается с уменьшением количества строк в буфере.
Есть несколько способов настроить выходную буферизацию . Основными из них являются:
- Отключите буферизацию в программах, использующих библиотеку stdio, без изменения настроек по умолчанию с помощью программы,
stdbuf -o0
найденной в GNU coreutils и некоторых других системах, таких как FreeBSD. Вы также можете переключиться на буферизацию строки с помощью stdbuf -oL
.
- Переключитесь на буферизацию строки, направив вывод программы через терминал, созданный специально для этой цели с помощью
unbuffer
. Некоторые программы могут вести себя по-другому, например, grep
по умолчанию используют цвета, если их выводом является терминал.
- Сконфигурируйте программу, например, перейдя
--line-buffered
в GNU grep.
Давайте посмотрим на фрагмент выше, на этот раз с буферизацией строки с обеих сторон.
{ 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
Так что на этот раз да никогда не прерывал grep, но grep иногда прерывал да. Я пойду, почему позже.
Чередование труб
Пока каждая программа выводит по одной строке за раз, а строки достаточно короткие, выходные строки будут аккуратно разделены. Но есть предел тому, как долго эти строки могут работать. Сам канал имеет буфер передачи. Когда программа выводит в канал, данные копируются из программы записи в буфер передачи канала, а затем из буфера передачи канала в программу чтения. (По крайней мере, концептуально - ядро может иногда оптимизировать это для одной копии.)
Если данных для копирования больше, чем умещается в буфере передачи канала, ядро копирует один буфер за раз. Если несколько программ пишут в один и тот же канал, и первая программа, которую выбирает ядро, хочет написать более одного буфера, тогда нет гарантии, что ядро выберет ту же самую программу во второй раз. Например, если P - размер буфера, он foo
хочет записать 2 * P байтов и bar
хочет записать 3 байта, то одно возможное перемежение - это P байтов из foo
, затем 3 байта из bar
и P байтов из foo
.
Возвращаясь к приведенному выше примеру «да + grep», в моей системе yes aaaa
пишется столько строк, сколько может поместиться в 8192-байтовый буфер за один раз. Поскольку необходимо записать 5 байтов (4 печатаемых символа и символ новой строки), это означает, что каждый раз записывается 8190 байт. Размер буфера канала составляет 4096 байт. Следовательно, можно получить 4096 байт из yes, затем некоторый вывод из grep, а затем остальную часть записи из yes (8190 - 4096 = 4094 байт). 4096 байт оставляет место для 819 строк с aaaa
одинокой a
. Следовательно, строка с этим lone a
сопровождается одной записью из grep, давая строку с abbbb
.
Если вы хотите увидеть подробности происходящего, getconf PIPE_BUF .
вам сообщат размер буфера канала в вашей системе, и вы увидите полный список системных вызовов, выполненных каждой программой с
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
Как гарантировать чистое чередование строк
Если длина строки меньше размера буфера канала, то буферизация строки гарантирует, что в выводе не будет смешанной строки.
Если длины строк могут быть больше, невозможно избежать произвольного микширования, когда несколько программ записывают в один канал. Чтобы обеспечить разделение, необходимо заставить каждую программу записывать в отдельный канал и использовать программу для объединения строк. Например, GNU Parallel делает это по умолчанию.