Скажем, я запускаю несколько процессов:
#!/usr/bin/env bash
foo &
bar &
baz &
wait;
Я запускаю приведенный выше скрипт так:
foobarbaz | cat
насколько я могу судить, когда любой из процессов записывает в stdout / stderr, их вывод никогда не перемежается - каждая строка stdio кажется атомарной. Как это работает? Какая утилита контролирует, насколько каждая строка атомарна?
Ответы:
Они чередуются! Вы пробовали только короткие пакеты вывода, которые остаются неразделенными, но на практике трудно гарантировать, что какой-либо конкретный вывод останется неразделенным.
Буферизация вывода
Это зависит от того, как программы буферизуют свой вывод. Библиотека stdio, которую большинство программ используют при написании, использует буферы для повышения эффективности вывода. Вместо вывода данных, как только программа вызывает библиотечную функцию для записи в файл, функция сохраняет эти данные в буфере и фактически выводит данные только после заполнения буфера. Это означает, что вывод осуществляется партиями. Точнее, есть три режима вывода:
Программы могут перепрограммировать каждый файл, чтобы вести себя по-разному, и могут явно очистить буфер. Буфер очищается автоматически, когда программа закрывает файл или завершает работу в обычном режиме.
Если все программы, которые пишут в один и тот же канал, либо используют режим буферизации строки, либо используют режим без буферизации и записывают каждую строку одним вызовом функции вывода, и если строки достаточно короткие, чтобы записать их в один фрагмент, то вывод будет чередование целых строк. Но если одна из программ использует полностью буферизованный режим или строки слишком длинные, вы увидите смешанные строки.
Вот пример, где я чередую вывод двух программ. Я использовал GNU coreutils в Linux; разные версии этих утилит могут вести себя по-разному.
yes aaaa
пишетaaaa
навсегда в том, что по существу эквивалентно режиму с линейной буферизацией.yes
Утилита на самом деле пишет несколько строк в то время, но каждый раз , когда он испускает выход, выход представляет собой целое число строк.echo bbbb; done | grep b
пишетbbbb
навсегда в режиме полной буферизации. Он использует размер буфера 8192, и каждая строка имеет длину 5 байт. Поскольку 5 не делит 8192, границы между записями вообще не находятся на границе строк.Давайте разберем их вместе.
Как видите, да иногда прерывается grep и наоборот. Только около 0,001% линий были прерваны, но это случилось. Выходные данные рандомизированы, поэтому количество прерываний будет меняться, но я видел по крайней мере несколько прерываний каждый раз. Если бы строки были длиннее, была бы более высокая доля прерванных линий, поскольку вероятность прерывания увеличивается с уменьшением количества строк в буфере.
Есть несколько способов настроить выходную буферизацию . Основными из них являются:
stdbuf -o0
найденной в GNU coreutils и некоторых других системах, таких как FreeBSD. Вы также можете переключиться на буферизацию строки с помощьюstdbuf -oL
.unbuffer
. Некоторые программы могут вести себя по-другому, например,grep
по умолчанию используют цвета, если их выводом является терминал.--line-buffered
в GNU grep.Давайте посмотрим на фрагмент выше, на этот раз с буферизацией строки с обеих сторон.
Так что на этот раз да никогда не прерывал 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
. Следовательно, строка с этим lonea
сопровождается одной записью из grep, давая строку сabbbb
.Если вы хотите увидеть подробности происходящего,
getconf PIPE_BUF .
вам сообщат размер буфера канала в вашей системе, и вы увидите полный список системных вызовов, выполненных каждой программой сКак гарантировать чистое чередование строк
Если длина строки меньше размера буфера канала, то буферизация строки гарантирует, что в выводе не будет смешанной строки.
Если длины строк могут быть больше, невозможно избежать произвольного микширования, когда несколько программ записывают в один канал. Чтобы обеспечить разделение, необходимо заставить каждую программу записывать в отдельный канал и использовать программу для объединения строк. Например, GNU Parallel делает это по умолчанию.
источник
cat
атомарно, так что процесс cat получает целые строки из foo / bar / baz, но не половину строки от одной и половину строки от другой и т. д. Есть ли что-то, что я могу сделать со скриптом bash?awk
было создано две (или более) строки вывода для одного идентификатора,find -type f -name 'myfiles*' -print0 | xargs -0 awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }'
но сfind -type f -name 'myfiles*' -print0 | xargs -0 cat| awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }'
ним правильно получена только одна строка для каждого идентификатора.http://mywiki.wooledge.org/BashPitfalls#Non-atomic_writes_with_xargs_-P рассмотрел это:
источник
xargs echo
не вызывает встроенную функцию echo bash, аecho
утилиту from$PATH
. И в любом случае я не могу воспроизвести это поведение bash echo с помощью bash 4.4. В Linux запись в канал (не / dev / null) больше 4K не гарантируется как атомарная.