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

виходу немає, поки не пройде одна секунда, і обидва рядки з’являться на виході негайно, чого я не очікував .


Чому це відбувається і як я можу змусити останню версію вести себе так само, як перші три команди?


catоб'єднує файли. Що ви намагаєтесь зробити, використовуючи труби cat?
Дуглас відбувся

15
@DouglasHeld Коли викликається без аргументів, catпросто читає stdinта виводить у stdout. Звичайно, я поставив це запитання з великою кількістю складних речей замість echoі cat, але це виявилося неактуальним, оскільки проблема виявляється набагато простішими прикладами.
lisyarus

3
@DouglasHeld: Передача котів часто корисна, щоб змусити stdout не бути терміналом. Наприклад, це простий спосіб отримати багато команд, щоб не використовувати кольоровий вихід.
wchargin

Я клянусь, це дублікат іншого питання щодо Stack Overflow!
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, якими вони користуються, роблять це, оскільки це визначена особливість програмування мовою С - на основі того, що вони визначають, що їх стандартний вихід. Якщо (і лише якщо) це не інтерактивний пристрій, вони обирають повну буферизацію, інакше вони вибирають розумну буферизацію. Труба вважається не інтерактивним пристроєм, оскільки визначення як інтерактивного пристрою, принаймні у світі Unix та Linux, по суті є isatty()викликом, що повертає істину для відповідного дескриптора файлів.

Обхідні шляхи відключення повного буферизації

Деякі утиліти, як-от, grepмають ідіосинкратичні варіанти, наприклад, --line-bufferedщо змінюють це рішення, яке, як ви бачите, неправильно названо. Але така фігура програм, які можна було використати, зникає на малі.

Більш загально, можна використовувати інструменти, які копаються у специфічній бібліотеці С та змінюють її прийняття рішень (які мають проблеми із безпекою, якщо програма, яку потрібно змінити, встановлена ​​UID, а також є специфічною для конкретних бібліотек С, і справді є специфічні для програм, написаних або розшарованих на мові С), або таких інструментів, ptybandageякі не змінюють внутрішні програми програми, а просто вставляють псевдотермінал як стандартний вихід, щоб рішення виходило як "інтерактивне", щоб впливати на це.

Подальше читання


1
Якщо словосполучення "буферизований рядок" є помилковим, то це насправді не вина grep, а основні дзвінки бібліотеки, setbuf/setvbuf . Я не знаю надійної онлайн-довідки для стандарту C, але, наприклад, керівні сторінки Linux і FreeBSD разом із описом POSIX setvbufназивають її "рядовим буфером". Навіть символічна константа для нього є _IOLBF.
ilkkachu

Ну, тепер ти краще навчився. Ця стратегія буферизації буде описана в бібліотеці DOCO GNU C, хоча і коротко. Лоран Беркот більш відвертий у цьому питанні. Я це також згадував.
JdeBP

Я не думав, що "Ваші очікування неправильні" було гарним заголовком для цього відмінного пояснення буферизації вихідних даних. Сподіваюсь, ви не заперечуєте, що я його видалив і додав кілька описових заголовків до кожного розділу відповіді.
Ентоні Г - справедливість для Моніки

2
@ilkkachu Стандарт C справді використовує "буферну лінію". Згідно з пунктом 3 файлів 7.21.3 файлів : "Коли потік розблоковується, ... Коли потік буферується повністю, ... Коли потік буферується для рядків, символи призначені для передачі в середовище хоста або з нього як блокувати, коли зустрічається символ нового рядка. ... "Насправді стандарт C п’ять разів використовує точну фразу" буферний рядок ". Тож це не помилка.
Ендрю Генле

1
Крім того, підхід, описаний тут як "розумне буферизація", наскільки я розумію, здається саме тим, що стандарт С характеризує як "буферизацію ліній". Зокрема, на додаток до промивання буфера в нових рядках: "Коли потік є буферизованим рядком, символи призначені для передачі до або з оточуючого середовища у вигляді блоку, коли запит [...] запитується в небуферованому потоці або коли вхід запитується в буферний потік для рядків, який вимагає передачі символів з хост-середовища. " Отже, це не химерність GNU чи BSD, а скоріше те, що вимагає мова.
Джон Боллінгер

7

Використовуйте

grep --line-buffered

щоб grep не буферував більше, ніж один рядок за один раз.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.