中間ファイルを作らないために
複数のコマンドをパイプで繋ぐのはよくやるけど、
$ command1 | command2
入出力が2つ以上あるコマンドを同じように繋げたい場合はどうしたらいいだろうか?
$ command1 -out1 output1 -out2 output2
$ command2 -in1 output1 -in2 output2
名前付きパイプを使う
mkfifo
コマンドを使うと名前付きパイプという特殊ファイルを作成できる。
こちらの記事が分かりやすい:mkfifoコマンドって使ってますか?
いつものパイプでは2プロセス間でストリーム1つ分しかやりとりできないけど、名前付きパイプなら必要な数だけ用意すればいくつでも対応できる。
$ mkfifo fifo1 fifo2
$ command1 -out1 fifo1 -out2 fifo2 &
$ command2 -in1 fifo1 -in2 fifo2
$ rm fifo1 fifo2
デッドロック対策
command1
が2つの出力先に交互に出力して、command2
も2つの入力を交互に読み込むような、コマンド間で入出力の順番がおおよそ一致している時はうまくいくが、場合によってはデッドロックが発生してしまう。
例えば、command1
は交互に出力するけどcommand2
がしばらくfifo1
の方ばかり読み込むことがある状況では、
-
command1
はfifo1
とfifo2
へ交互に書き込む -
fifo2
にだけデータが溜まっていく -
fifo2
のバッファがいっぱいになる -
fifo2
はcommand1
からの書き込みをブロックする - 特に対策がなければ
command1
はfifo2
のバッファが空くのを待つことになる -
command2
がfifo1
を空にする -
command2
がいくら待ってもfifo1
からデータは流れてこない - _人人人人人人人人_
> デッドロック <
 ̄Y^Y^Y^Y^Y^Y^Y ̄
みたいなことがある。
パイプのバッファサイズは場合によるが、せいぜい64KiBのようだ1。
対策としては、間に適当なサイズのバッファを挟むのが楽だと思われる。例えばmbufferコマンドなどがいい感じ。
$ mkfifo fifo1 fifo2
$ command1 -out1 fifo1 -out2 fifo2 &
$ command2 -in1 <(mbuffer -q -i fifo1) -in2 <(mbuffer -q -i fifo2)
$ rm fifo1 fifo2
参考
-
How big is the pipe buffer? - パイプのバッファサイズを確認できるスクリプトの例もある ↩