Linuxの「漏れやすい」パイプ


12

次のようなパイプラインがあるとします。

$ a | b

bstdinの処理を停止した場合、しばらくしてからパイプがいっぱいになり、astdoutへの書き込みがブロックされます(b処理が再開されるか、終了するまで)。

これを回避したい場合は、次のように大きなパイプ(または、もっと簡単に言うとbuffer(1))を使用したくなるかもしれません。

$ a | buffer | b

これは単に私にもっと時間を買ってくれるでしょう、しかしa結局結局止まるでしょう。

(私が取り組んでいる非常に具体的なシナリオの場合)欲しいのは、「リークの多い」パイプを使用して、いっぱいになるとバッファからデータを(理想的には行ごとに)ドロップしてa続行できるようにすることです処理(おそらくご想像のとおり、パイプを流れるデータは使い捨てです。つまり、データを処理することは、ブロックなしで実行できることbほど重要ではありませんa)。

要約すると、境界のある、リーキーなバッファのようなものが欲しいです:

$ a | leakybuffer | b

おそらくどの言語でも簡単に実装できますが、「すぐに使える」もの(またはbashの1行のようなもの)がないのではないかと思っていました。

注:例では通常のパイプを使用していますが、質問は名前付きパイプにも同様に適用されます


私は以下の回答を授与しましたが、以下の簡単なソリューションにはいくつかの制限があったため、leakybufferコマンドを実装することも決定しました:https : //github.com/CAFxX/leakybuffer


名前付きパイプは本当にいっぱいですか?名前付きパイプこれに対する解決策であると思っていたでしょうが、確かには言えませんでした。
ワイルドカード

3
名前付きパイプは、無名のパイプとして(デフォルトでは)私の知る限り、同じ能力を持っている
CAFxX

回答:


14

最も簡単な方法は、ノンブロッキング出力を設定するプログラムをパイプすることです。これは単純なperl 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 2017

@Wallacolooそれを指摘してくれてありがとう、私のケースはログファイルのストリーミングでした...バイナリファイルをサポートするために必要なわずかな変更については、更新された回答を参照してください。
Matija Nalis 2017

また、GNU参照ddのをdd oflag=nonblock status=none
ステファンChazelas

1
申し訳ありませんが、私の悪いが、再び、実際にそう、アトミックであることが保証されている(POSIXによって少なくとも512であることが必要、Linux上で4096)PIPE_BUFはバイト未満の書き込み$| = 1や、あなたのsyswrite()アプローチは確かのように長い行が合理的に不足しているように短い書き込みを防止します。
ステファンChazelas
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.