Предположим, у вас есть конвейер, подобный следующему:
$ a | b
Если b
остановка обработки stdin, через некоторое время канал заполняется, и запись, начиная a
с его stdout, будет блокироваться (пока либо не b
начнется обработка снова, либо он не умрет).
Если бы я хотел избежать этого, у меня мог бы возникнуть соблазн использовать более крупную трубу (или, проще, buffer(1)
) следующим образом:
$ a | buffer | b
Это просто выиграет мне больше времени, но в конце a
концов остановится.
Что я хотел бы иметь (для очень специфического сценария, к которому я обращаюсь), так это иметь «негерметичный» канал, который при заполнении удалял бы некоторые данные (в идеале построчно) из буфера, чтобы позволить a
продолжить обработка (как вы, вероятно, можете себе представить, данные, которые передаются по конвейеру, являются расходуемыми, т. е. обработка данных b
менее важна, чем a
возможность работать без блокировки).
Подводя итог, я хотел бы иметь что-то вроде ограниченного, протекающего буфера:
$ a | leakybuffer | b
Я мог бы реализовать это довольно легко на любом языке, мне просто было интересно, есть ли что-то «готовое к использованию» (или что-то вроде однострочного bash), которое мне не хватает.
Примечание: в примерах я использую обычные каналы, но вопрос в равной степени относится и к именованным каналам.
Хотя я получил ответ ниже, я также решил реализовать команду leakybuffer, поскольку простое решение, приведенное ниже, имеет некоторые ограничения: https://github.com/CAFxX/leakybuffer
Ответы:
Проще всего было бы передать через некую программу, которая устанавливает неблокирующий вывод. Вот простой Perl Oneliner (который вы можете сохранить как Lakyybuffer ), который делает это:
так твоя
a | b
становится:то, что делает, это читает входные данные и записывает на выход (так же, как
cat(1)
), но выходные данные не являются блокирующими - это означает, что если запись завершится неудачно, он вернет ошибку и потеряет данные, но процесс продолжится со следующей строки ввода, поскольку мы удобно игнорируем ошибка. Процесс вроде как буферизован, как вы хотели, но смотрите предостережение ниже.Вы можете проверить, например:
вы получите
output
файл с потерянными строками (точный вывод зависит от скорости вашей оболочки и т. д.) примерно так:Вы видите, где оболочка теряла строки после
12773
, но также и аномалию - у perl не было достаточно буфера,12774\n
но он сделал для этого,1277
поэтому он записал только это - и поэтому следующее число75610
не начинается в начале строки, что делает его небольшим некрасиво.Это можно улучшить, если perl определит, когда запись не удалась полностью, а затем попытаться сбросить оставшуюся часть строки, игнорируя при этом новые поступающие строки, но это значительно усложнит сценарий perl, поэтому оставляется в качестве упражнения для заинтересованный читатель :)
Обновление (для двоичных файлов): если вы не обрабатываете строки, заканчивающиеся символом новой строки (например, файлы журнала или аналогичные), вам нужно слегка изменить команду, иначе perl будет занимать большие объемы памяти (в зависимости от того, как часто символы ввода строки появляются в ваших входных данных):
он также будет работать правильно для двоичных файлов (без использования дополнительной памяти).
Update2 - более хороший вывод текстового файла: Избегание выходных буферов (
syswrite
вместоprint
):кажется, исправить проблемы с "объединенными линиями" для меня:
(Примечание: можно проверить, на каких линиях был обрезан вывод:
perl -ne '$c++; next if $c==$_; print "$c $_"; $c=$_' output
oneliner)источник
perl -w -MFcntl -e 'fcntl STDOUT,F_SETFL,O_WRONLY|O_NONBLOCK; while (<STDIN>) { print }' | aplay -t raw -f dat --buffer-size=16000
, perl, кажется, постоянно выделяет больше памяти, пока не будет уничтожен менеджером OOM.dd
«Sdd oflag=nonblock status=none
.$| = 1
вашsyswrite()
подход действительно предотвращает короткие записи, пока строки достаточно короткие.