"Негерметичні" труби в Linux


12

Припустимо, у вас є такий трубопровід:

$ a | b

Якщо bзупиняє обробку stdin, через деякий час труба заповнюється, і запис, починаючи aз її stdout, блокується (доки або bзнову не почне обробку, або вона вмирає).

Якби я хотів цього уникнути, я міг би спокусити використовувати більшу трубу (або, простіше кажучи, buffer(1)) так:

$ a | buffer | b

Це просто придбало б мені більше часу, але aврешті-решт воно зупиниться.

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

Підсумовуючи це, я хотів би мати щось на кшталт обмеженого, протікаючого буфера:

$ a | leakybuffer | b

Я, мабуть, міг би реалізувати це досить легко на будь-якій мові, мені було просто цікаво, чи є щось, що "готове до використання" (або щось на зразок баш-однолінійного), що мені не вистачає.

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


Хоча я присуджував відповідь нижче, я також вирішив застосувати команду leakybuffer, оскільки просте рішення нижче має деякі обмеження: https://github.com/CAFxX/leakybuffer


Чи справді заповнені названі труби? Я б подумав, що названі труби є рішенням цього, але я не міг сказати точно.
Wildcard

3
Названі труби мають (за замовчуванням) таку ж ємність, як і неназвані труби, AFAIK
CAFxX

Відповіді:


14

Найпростішим способом було б проходження через якусь програму, яка встановлює неблокуючий вихід. Ось простий perlin oneliner (який можна зберегти як leakybuffer ), який робить так:

тому ваш a | bстає:

a | perl -MFcntl -e \
    'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { print }' | b

що є читання вводу та запис у вихід (такий же, як cat(1)), але вихід неблокуючий - це означає, що якщо запис не вдасться, він поверне помилку і втратить дані, але процес буде продовжуватися наступним рядком введення, оскільки ми зручно ігноруємо помилка. Процес виглядає так, як ви хотіли, але див. Застереження нижче.

Ви можете протестувати, наприклад:

seq 1 500000 | perl -w -MFcntl -e \
    'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { print }' | \
    while read a; do echo $a; done > output

ви отримаєте outputфайл із втраченими рядками (точний вихід залежить від швидкості вашої оболонки тощо) так:

12768
12769
12770
12771
12772
12773
127775610
75611
75612
75613

ви бачите, де оболонка втратила рядки після 12773, але також і аномалія - ​​у perl не вистачало буфера, але це робиться 12774\nдля 1277цього, і він написав саме це - і тому наступне число 75610не починається на початку рядка, що робить його мало потворний.

Це можна покращити, встановивши perl, коли запис не вдався повністю, а потім спробуйте перекинути залишок рядка, ігноруючи при цьому нові рядки, але це значно ускладнить сценарій perl набагато більше, тому залишається як вправа для зацікавлений читач :)

Оновлення (для двійкових файлів): Якщо ви не обробляєте завершені рядки нового рядка (наприклад, файли журналів чи подібні), вам потрібно трохи змінити команду, або perl буде споживати велику кількість пам'яті (залежно від того, як часто символи нового рядка з’являються у вашому введенні):

perl -w -MFcntl -e 'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (read STDIN, $_, 4096) { print }' 

він буде працювати правильно і для двійкових файлів (не витрачаючи зайвої пам'яті).

Update2 - приємніше виведення текстового файлу: уникання вихідних буферів ( syswriteзамість print):

seq 1 500000 | perl -w -MFcntl -e \
    'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { syswrite STDOUT,$_ }' | \
    while read a; do echo $a; done > output

здається, виправляє проблеми зі "об'єднаними лініями" для мене:

12766
12767
12768
16384
16385
16386

(Примітка: можна перевірити, з яких рядків було вирізано: perl -ne '$c++; next if $c==$_; print "$c $_"; $c=$_' outputoneliner)


Мені подобається oneliner: я не експерт по Perl, якщо хто-небудь може запропонувати покращення вище, це було б приголомшливо
CAFxX

1
Це, здається, працює певною мірою . Але, як я спостерігаю за своєю командою, яка є 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.
Ponkadoodle

@Wallacoloo спасибі за те, що вказав, що в моєму випадку були потокові файли журналів ... Дивіться оновлений відповідь про незначні зміни, необхідні для підтримки бінарних файлів.
Matija Nalis

Див. Також GNU dd's dd oflag=nonblock status=none.
Стефан Шазелас

1
Вибачте, знову ж таки погано, насправді записи меншими, ніж байпи PIPE_BUF (4096 в Linux, для POSIX має бути не менше 512), гарантовано є атомними, тому $| = 1ваш syswrite()підхід перешкоджає короткому запису дійсно до тих пір, поки рядки досить короткі.
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.