2つの非同期サブシェルコマンドを共有標準出力に安全に書き込むことはできますか?

2つの非同期サブシェルコマンドを共有標準出力に安全に書き込むことはできますか?

非同期的に実行される2つのbourne(または重要な場合はbash)サブシェルコマンドでstdoutを上書きできますか?

(tail -f ./file1 & tail -f ./file2) | cat

行の順序は気にせず、各出力行が1つの入力行で構成される点だけ異なります。一部の行が部分的に隠れているかインターリーブされる可能性があることが心配です。

私はそれぞれ独自のラインを1,500万回出力する4つのコマンドを実行してこれをテストしました。うまくいくようですが、失敗すると予想しました。

誰かがこれがどのように壊れないかを説明できますか?各サブシェルはバッファリングされており、一度に1つのサブシェルのみを標準出力に書き込むことができますか?またはこれがどのように管理されるか。

もっと良い方法がありますか?

(私がそうであることを気にしないでください。使用tail上記のサブシェルでは例示目的。実際には、一度に1行ずつ標準出力に連続して出力する2つの異なるコマンドを実行したいと思います。 )

答え1

シェルはそこにほとんど関与していません。彼らがすることはパイプラインを生成し、この3つのコマンドを実行することだけです。その後、このコマンドはシェルとは無関係に並列に実行されます。

ここで重要なことは、両方のtailコマンドが同じパイプの同じ書き込みの最後にファイル記述子を書き込むことです。

これにより:

printf foo1 >> file1; sleep 1
printf foo2 >> file2; sleep 1
printf 'bar1\n' >> file1; sleep 1
printf 'bar2\n' >> file2

あなたが見ることができるもの:

foo1foo2bar1
bar2

なぜならそう書かれているからです。コマンドが以下を出力することを確認する必要があります。いっぱい一度に 1 つの行を書き込み、行は PIPE_BUF (Linux の場合は 4096 バイト) より小さく、write() がアトミックであることを保証します (すべてが完全で累積サイズが小さい場合は複数の行全体を同時に書き込むこともできます) 。 PIPE_BUFより)。

GNUを使用すると、grep次のコマンドを渡してこれを実行できます。grep --line-buffered '^'

(tail -f ./file1 | grep --line-buffered '^' &
 tail -f ./file2 | grep --line-buffered '^') | cat

これにより、2つのコマンド出力の各行にwrite()システムコールが含まれます(コマンドが出力の最後の行を終了しない場合は、行が不足している行が追加さgrepれます)。

答え2

1. 悪い解決

デフォルト設定では、stderrはいいえバッファリングされていますが、標準出力はバッファリングされています。

したがって、問題に対する最も簡単な解決策は

  1. すべてを1行ずつ作成するために使用されるツールを提供します。
  2. 出力を stderr( >&2) にリダイレクトします。

ただし、このバッファリングはプロセス内のCライブラリで発生するため機能しません。標準出力が標準エラーにリダイレクトされると、その標準エラーもバッファリングされません。

2. より良いソリューション

出力をすべてを読み取るプロセスにパイプし、1行ずつ記録します。最も簡単な方法は

tool1 | while read; do echo "$REPLY"; done & tool2 | while read; do echo "$REPLY"; done

複数のコマンド/スクリプトの「美しい」並列実行のために、ここ私の他の答えを読むことができます。

3. 実際のソリューション

write(1, ...)残念ながら、プロセス出力はほとんどlibcによってバッファリングされます。 libcの内部コンテンツは、出力をカーネルレベルの呼び出しにマップする方法です。それは彼らに依存します。これを変更しないと、まだ作成されていない出力を制御できません。

FILE*libcのメカニズムを書き込みに使用する場合、バッファ設定(3)そしてフラッシュ(3)関数はあなたの友人になることができます。

関連情報