名前付きパイプから一定サイズのバッファを読み込み、各バッファに対してコマンドを実行する方法

名前付きパイプから一定サイズのバッファを読み込み、各バッファに対してコマンドを実行する方法

端末には次の機能がありますredis-cli -x PUBLISH myChannel。このデバイスからデータを読み取り、stdin操作を実行します。

FIFO自体はチャンクデータを提供しません。したがって、ストリームからチャンクを作成し、チャンクごとredis-cli -x PUBLISH myChannelに実行する必要があります。

私はこれを試しました: { while :; do dd iflag=fullblock iflag=nonblock bs=65536 count=1 2> /dev/null | redis-cli -x PUBLISH myChannel ; done } < myFifo

動作中です。しかし、CPU消費量が高い。そしてこれは拡張にはあまりよくありません(50個の同時命令のために40%のCPU使用率が発生します)。

@terdonが提供したソリューションも試してみました。[ここ]:

{ 
  while :; do 
    dd iflag=fullblock iflag=nonblock bs=65536 count=1 2> /dev/null | 
      redis-cli -x PUBLISH myChannel 
    sleep 0.1
  done 
} < myFifo

少ないCPUを使用します(同時コマンド50個に対して20%)。しかし、依然として大きな規模の問題が発生する可能性があります。

私はまた、以下を使用してtail -FFiFoから読み取ろうとしました。@raffaが提案したように:

tail -c +1 -F myFifo | 
  dd iflag=fullblock bs=65536 2> /dev/null | 
    redis-cli -x PUBLISH myChannel

redis-cli -x PUBLISH myChannel残念ながら、これはすべてのブロックに対して実行する必要があるため機能しません。データストリームを受信できません。

このコマンドの機能を実行しながらCPU効率を向上させる方法はありますか?ソリューションが次のステップにあることを追加することもできます。Ubuntu 16.04以上

とても感謝しています。

ベストアンサー1

私はこれを試しました:{ while :; do dd iflag=fullblock iflag=nonblock bs=65536 count=1 2> /dev/null | redis-cli -x PUBLISH myChannel ; done } < myFifo

動作中です。しかし、CPU消費量が高い。そしてこれは拡張にはあまりよくありません(50個の同時命令のために40%のCPU使用率が発生します)。

iflag=nonblockGNU coreutils ddは非ブロック読み取りを使用する必要があります。つまり、読み取るデータがない場合は、オペレーティングシステムのハンドルを離れてより多くのデータを待たずに、エラーコードEAGAINまたはEWOULDBLOCKを使用して制御が即座にプロセスに返されます。 dd はこれを他のエラーと同様に処理し、エラーメッセージを印刷して終了するようです。ループはddを再起動するため、同じ状況が急速に連続して発生し続けます。もちろん、これはCPU使用率を高めます。

エラーにリダイレクトしないと、/dev/null次のようにdd出力が表示され、何もせずに短時間で繰り返し実行される方法を明確に示しています。

dd: error reading 'standard input': Resource temporarily unavailable
0+0 records in
0+0 records out
0 bytes copied, 4.3734e-05 s, 0.0 kB/s
[...]

提案:

1)何かをテストしたり、最善の方法を試したりする場合いいえエラーを隠すことは非常に便利です。

2) データを読み込んでいて他に何もしない場合は、システムコールをブロックします。

アンインストールはiflag=nonblockこの問題を解決するのに非常に役立ちます。

また、ddが必要かどうかはわかりません。head -c 65536同様の作業を行う必要があります。


しかし、読んでいるパイプが閉じているときに何が起こるかはまだ開いています。この時点で、すべての読み取りは(成功的に)0バイトを返し、データストリームの終わりを示します。この時点で、ddは0バイトを読み取って終了し、ループは再開し続けます。残念ながら、これを検出することはより困難です。これはddに関する限り、まだ成功した実行だからです。

データを渡す前に一時ファイルに読み込むことを選択できるため、続行することを決定する前にファイルサイズを確認するか、ddのstderr出力を読み取ってコピーされたデータの量を確認できます。 (またはデータをシェル変数として読み込みますが、ほとんどのシェルでは8ビットクリーンではありません。zshは例外です。)

通常、エラーが発生したときにループを中断できるように、ddのシャットダウン状態も確認する必要がありますが、0バイトの実行も確認する必要があります。

あるいは、読み書きモードでFIFOを開くことができるため、実際のビルダーが閉じたり、ddループhead内でそれ自体がブロックされたりすることがあります。ループは終了しませんが、少なくとも忙しいループに陥ることはありません。

おそらく次のようになります。

while true; do head -c 65536| redis ... ; done 0<> fifo

headまたは失敗または失敗した場合は、ループを中断しますredis

set -o pipefail
while head -c 65536 | redis ... ; do true; done 0<> fifo

とにかく、このようなものはPerlのようなシェル以外のものから、またはいくつかの既存のツールを使用してよりよく実装できます。 (良いsplit)。

おすすめ記事