Трубы, как поток данных в конвейере?

22

Я не понимаю, как данные передаются по конвейеру, и надеюсь, что кто-то сможет прояснить, что там происходит.

Я думал, что конвейер команд обрабатывает файлы (текст, массивы строк) построчно. (Если каждая команда работает построчно.) Каждая строка текста проходит через конвейер, команды не ждут, пока предыдущая закончит обработку всего ввода.

Но, похоже, это не так.

Вот тестовый пример. Есть несколько строк текста. Я пишу их прописными буквами и повторяю каждую строку дважды. Я делаю это с cat text | tr '[:lower:]' '[:upper:]' | sed 'p'.

Чтобы следить за процессом, мы можем запустить его «в интерактивном режиме» - пропустите входное имя файла в cat. Каждая часть трубопровода проходит построчно:

$ cat | tr '[:lower:]' '[:upper:]'
alkjsd
ALKJSD
sdkj
SDKJ
$ cat | sed 'p'
line1
line1
line1
line 2
line 2
line 2

Но полный конвейер действительно ждет, пока я закончу ввод, EOFи только затем напечатает результат:

$ cat | tr '[:lower:]' '[:upper:]' | sed 'p'
I am writing...
keep writing...
now ctrl-D
I AM WRITING...
I AM WRITING...
KEEP WRITING...
KEEP WRITING...
NOW CTRL-D
NOW CTRL-D

Так и должно быть? Почему это не построчно?

xealits
источник
Это не труба, это catбуферизация, пока не закроется стандартный ввод.
Златовласка
но trи sedделайте строки процесса catдо того, как stdin закрывается
xealits
Стандартные значения, используемые stdio (которые, как я полагаю, используют все упомянутые программы), заключаются в том, что stderr не буферизован, а stdout буферизуется строкой при записи в терминал и полностью буферизируется в противном случае (например, если он записывает в файл или канал) , У некоторых команд есть флаги, которые могут изменить буферизацию stdout, но похоже, что tr нет.
Касперд

Ответы:

36

Существует общее правило буферизации, которому следует стандартная библиотека ввода-вывода C ( stdio), которую использует большинство программ unix. Если вывод идет на терминал, он сбрасывается в конце каждой строки; в противном случае он сбрасывается только тогда, когда буфер (8K в моей системе Linux / amd64; может отличаться в вашей) заполнен.

Если все ваши утилиты следовали общему правилу, вы увидите выход с задержкой во всех ваших примерах ( cat|sed, cat|trи cat|tr|sed). Но есть исключение: GNU catникогда не буферизует свой вывод. Он либо не использует, stdioлибо изменяет stdioполитику буферизации по умолчанию .

Я могу быть уверен, что вы используете GNU, catа не какой-то другой Unix, catпотому что другие не будут себя так вести. Традиционный Unix catимеет -uвозможность запрашивать небуферизованный вывод. GNU catигнорирует эту -uопцию, потому что ее вывод всегда небуферизован.

Поэтому всякий раз, когда у вас есть канал с символом catслева, в системе GNU прохождение данных через канал не будет задерживаться. catДаже не собирается построчно - ваш терминал делает. Пока вы вводите данные для cat, ваш терминал находится в «каноническом» режиме - на основе строки, с клавишами редактирования, такими как backspace и ctrl-U, которые дают вам возможность редактировать введенную вами строку перед отправкой Enter.

В этом cat|tr|sedпримере trон по-прежнему получает данные catсразу после нажатия Enter, но trпридерживается stdioполитики по умолчанию: его выходные данные отправляются в канал, поэтому он не сбрасывается после каждой строки. Он записывает во второй канал, когда буфер заполнен или когда получен EOF, в зависимости от того, что наступит раньше.

sedтакже следует stdioполитике по умолчанию, но его выходные данные отправляются на терминал, поэтому он будет записывать каждую строку, как только закончил с ней. Это влияет на то, сколько вы должны набрать, прежде чем что-то появится на другом конце конвейера - если бы sedблок-буферизировал свой вывод, вам пришлось бы печатать вдвое больше (чтобы заполнить trвыходной буфер и sed выходные данные буфер).

У GNU sedесть -uопция, поэтому, если вы измените порядок и используете, cat|sed -u|trвы увидите, что вывод снова появится мгновенно. ( sed -uОпция может быть доступна в другом месте, но я не думаю, что это древняя традиция Unix, как cat -u) Насколько я могу судить, нет эквивалентной опции для tr.

Существует утилита, stdbufкоторая позволяет вам изменять режим буферизации любой команды, которая использует stdioзначения по умолчанию. Это немного хрупко, поскольку она использует LD_PRELOADдля достижения чего-то, что библиотека C не была разработана для поддержки, но в этом случае, похоже, работает:

cat | stdbuf -o 0 tr '[:lower:]' '[:upper:]' | sed 'p'

источник
1
Благодарность! Потрясающий ответ. Вероятно, я должен упомянуть буферизацию в вопросе так или иначе, чтобы можно было ее найти.
xealits
teeа ddтакже обычно играют по своим правилам. При творческом объединении эти три инструмента могут практически полностью устранить любые потребности stdbufв фоновых трубопроводах.
mikeserv
1
Это одна из причин избежать бесполезного использования кота .
Хоббс
8

На самом деле это заставило меня задуматься и еще больше ответить. Отличный вопрос (я опишу его дальше).

Вы забыли попробовать tr | sedсвои отладочные элементы выше:

>tr '[:lower:]' '[:upper:]' | sed 'p'
i am writing
still writing
now ctrl-d
I AM WRITING
I AM WRITING
STILL WRITING
STILL WRITING
NOW CTRL-D
NOW CTRL-D
>

Так что видимо trбуферы. Узнавайте что-то новое каждый день!

РЕДАКТИРОВАТЬ :

Пока я думаю об этом, мы выделили причину, но не дали объяснения. Если cat | trон пишет сразу же, если вы cat | sed, он пишет сразу, но если вы tr | sed, она ждет за EOF. Я хотел бы предложить ответ может быть похоронен в trили sedисходный код , то и не быть проблемой трубы.

РЕДАКТИРОВАТЬ :

Я вижу, что Wumpus предоставил объяснение, когда я печатал последнее редактирование. Благодарность!

Пуассон Aerohead
источник
1
действительно они буферизируют! и тест с примерно 8kb строками, как упомянул Wumpus, показывает, что буфер действительно 8Kb. Я хотел бы принять оба ответа, чтобы поделиться некоторой репутацией, но я возьму Wumpus как более полный. Спасибо, в любом случае!
xealits
1
Нет проблем, мой был эмпирическим ответом, его был знающий.
Пуассон Aerohead
Смотрите также этот вопрос, который показывает, как использовать, stdbufкоторый также может быть полезным. unix.stackexchange.com/questions/182537/…
Джо,