grep не выводит до EOF, если пропущен через cat

19

Учитывая этот минимальный пример

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

он выводит, LINE 1а затем, через одну секунду, выводит LINE 2, как и ожидалось .


Если мы передадим это grep LINE

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

поведение такое же, как и в предыдущем случае, как и ожидалось .


Если, в качестве альтернативы, мы передаем это cat

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

поведение снова такое же, как и ожидалось .


Однако , если мы передадим grep LINE, а затем cat,

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

нет выхода, пока не пройдет одна секунда, и обе строки сразу появятся на выходе, чего я не ожидал .


Почему это происходит и как я могу заставить последнюю версию вести себя так же, как первые три команды?

lisyarus
источник
catобъединяет файлы. Что вы пытаетесь сделать, проникая в cat?
Дуглас состоялся
15
@DouglasHeld Когда вызывается без аргументов, catпросто читает stdinи выводит в stdout. Конечно, я придумал этот вопрос с большим количеством сложных вещей вместо echoи cat, но они оказались неактуальными, так как проблема обнаруживается с гораздо более простыми примерами.
Лисярус
3
@DouglasHeld: Трубопровод к коту часто полезен, чтобы стандартный вывод не был терминалом. Например, это простой способ заставить многие команды не использовать цветной вывод.
wchargin
Клянусь, это дубликат другого вопроса о переполнении стека!
iBug
@wchargin большое спасибо, вы научили меня чему-то новому в posix, чего я никогда не знал.
Дуглас состоялся

Ответы:

38

Когда (по крайней мере, GNU) grepвывод не является терминалом, он буферизует свой вывод, что и вызывает поведение, которое вы видите. Вы можете отключить этот либо с помощью GNU grep«сек --line-bufferedвариант:

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

или stdbufутилита:

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

Отключить буферизацию в трубе есть еще по этой теме.

Стивен Китт
источник
26

Упрощенное объяснение

Подобно многим утилитам, это не является чем-то особенным для одной программы, и grepее стандартный вывод варьируется от линейной буферизации до полной буферизации . В первом случае библиотека C буферизует выходные данные в памяти до тех пор, пока либо буфер, содержащий эти данные, не будет заполнен, либо к нему не будет добавлен символ перевода строки (или программа завершится корректно), после чего она вызывает write()фактическую запись содержимого буфера. В последнем случае только заполнение буфера в памяти (или корректное завершение программы) вызывает write().

Более подробное объяснение

Это хорошо известное, но слегка ошибочное объяснение. Фактически, стандартный вывод не буферизован строкой, а умным буферизован в библиотеке GNU C и библиотеке BSD C. Стандартный вывод также сбрасывается, когда при чтении стандартного ввода исчерпывается его буфер в памяти (для ввода с предварительным чтением), и библиотека C должна вызывать, read()чтобы получить еще какой-то ввод, и она читает начало новой строки. (Одной из причин этого является предотвращение взаимоблокировки, когда другая программа подключается к обоим концам фильтра и ожидает, что сможет работать построчно, чередуя запись в фильтр и чтение из него; например, «сопроцессы» в GNU awkнапример.)

Влияние библиотеки C

grepи другие утилиты делают это - или, точнее, библиотеки C, которые они используют, делают это, потому что это определенная особенность программирования на языке C - на основе того, что они обнаруживают в своем стандартном выводе. Если (и только если) это не интерактивное устройство, они выбирают полную буферизацию, в противном случае они выбирают интеллектуальную буферизацию. Канал считается не интерактивным устройством, потому что определение интерактивного устройства, по крайней мере в мире Unix и Linux, по сути является isatty()вызовом, возвращающим true для соответствующего файлового дескриптора.

Обходные пути для отключения полной буферизации

Некоторые утилиты, такие как, grepимеют специфические опции, например, --line-bufferedкоторые изменяют это решение, которое, как вы можете видеть, имеет неправильное название. Но на самом деле очень малая часть программ-фильтров, которые можно было бы использовать, на самом деле имеет такую ​​возможность.

В более общем смысле, можно использовать инструменты, которые копаются во внутренних структурах библиотеки C и изменяют ее принятие решений (которые имеют проблемы с безопасностью, если программа, для которой нужно изменить, имеет установленный UID, а также специфичны для конкретных библиотек C и действительно являются специфичные для программ, написанных на языке C или наложенных поверх него), или такие инструменты, ptybandageкоторые не изменяют внутреннюю часть программы, а просто вставляют псевдотерминал в качестве стандартного вывода, так что решение выходит «интерактивным», чтобы повлиять на это.

дальнейшее чтение

JdeBP
источник
1
Если фраза «строка буферизована» неверна, то это не вина grep, а вызовы базовой библиотеки, setbuf/setvbuf . Я не знаю надежного онлайн-справочника по стандарту C, но, например, справочные страницы по Linux и FreeBSD вместе с описанием POSIX setvbufназывают его «буферизованной строкой». Даже символическая константа для него есть _IOLBF.
ilkkachu
Ну, теперь вы узнали лучше. Эта стратегия буферизации будет описана в библиотеке DOCO GNU C, хотя и кратко. Лоран Берко более откровенен в этом вопросе. Я тоже об этом упомянул.
JdeBP
Я не думал, что «Ваше ожидание неверно» было хорошим заголовком для этого превосходного объяснения буферизации вывода. Надеюсь, вы не возражаете, что я удалил его и добавил несколько описательных заголовков для каждого раздела ответа.
Энтони Дж. - правосудие для Моники
2
@ilkkachu Стандарт C действительно использует "буферизованную строку". Согласно разделу 7.21.3 «Файлы» , пункт 3 : «Когда поток не буферизирован, ... Когда поток полностью буферизован, ... Когда поток буферизуется строкой, символы предназначены для передачи в или из среды хоста как блокировать при обнаружении символа новой строки. ... "Фактически, стандарт C использует точную фразу" буферизованная строка "пять раз. Так что это не неправильно.
Эндрю Хенле
1
Кроме того, подход, описанный здесь как «умная буферизация», насколько я понимаю, кажется, что именно то, что стандарт C описывает как «линейная буферизация». В частности, помимо очистки буфера в новых строках: «Когда поток буферизуется строкой, символы предназначены для передачи в или из среды хоста в виде блока, когда [...] запрашивается ввод в небуферизованном потоке или когда вход запрашивается в потоке с буферизацией строки, который требует передачи символов из среды хоста. " Так что это не причуды GNU или BSD, а то, что требует язык.
Джон Боллинджер
7

использование

grep --line-buffered

чтобы grep не буферизовал более одной строки за раз.

choroba
источник