次のような、名前付きパイプ(fifo) を利用するスクリプトを記述しました。

#!/bin/sh

set -ex

rm -rf fifo
mkfifo fifo

cat <<EOF > fifo &
hoge
fuga
moge
EOF

while read -r line < fifo
do
    echo $line
done

echo "EOF!"

これを、 Mac OS X で実行した時には、途中で処理が止まってしまいます。 (Ctrl+C で中止している)

$ ./test-fifo.sh
+ rm -rf fifo
+ mkfifo fifo
+ read -r line
+ cat
+ echo hoge
hoge
+ read -r line
^C

一方、 ubuntu(14.04, dash0.5.7) 上で実行した場合には、意図通りにスクリプトは実行できています。

$ ./test-fifo.sh 
+ rm -rf fifo
+ mkfifo fifo
+ read -r line
+ cat
+ echo hoge
hoge
+ read -r line
+ echo fuga
fuga
+ read -r line
+ echo moge
moge
+ read -r line
+ echo EOF!
EOF!

質問:

  • どうして、このような OS 間の挙動の差異が発生するのでしょうか。

仮説(考えたこと)

  1. このスクリプトの挙動はそもそも undefined である。

    • ただ、だとするとその旨の記述はどこかに仕様として記述があるのでは、と考えていて、それが見つけられないと考えている状態です。
  2. OS 間で fifo の挙動が、仕様として差異がある。

    • この場合も同様に、その仕様がどこかにまとまっていると考えていますが、それをどうやったら探せるのかわからないと思っている状態です。

追記@ 2016/04/03

手元の ubuntu 環境(14.04) だと、 bash でも dash でも、同じようによろしく動いてしまっています。。

bash

$ bash test-fifo.sh 
+ rm -rf fifo
+ mkfifo fifo
+ read -r line
+ cat
+ echo hoge
hoge
+ read -r line
+ echo fuga
fuga
+ read -r line
+ echo moge
moge
+ read -r line
+ echo 'EOF!'
EOF!

dash

$ dash test-fifo.sh 
+ rm -rf fifo
+ mkfifo fifo
+ read -r line
+ cat
+ echo hoge
hoge
+ read -r line
+ echo fuga
fuga
+ read -r line
+ echo moge
moge
+ read -r line
+ echo EOF!
EOF!

自分の手元の、よろしく動いてしまう ubuntu で、 dash/bash の strace を見る限りは、次のような動作をしていました。

  1. hoge\nfuga\nmoge を cat するプロセスが、この文字列を fifo に write する。 write システムコールが発行されて、このプロセスの実行がしばらく止まる。
  2. シェルが1文字ずつ read する。 hoge\n になったら fifo を close する。
  3. シェルが echo 処理を行う
  4. シェルが再び fifo をオープンして、 手順の 2 に戻る
  5. 2-4 を繰り返して、hoge\nfuga\nmoge\nがなくなると、 cat プロセスに実行が戻る。 cat プロセスが正常終了する。
  6. read で何も読み込めなかったので、 while のループを抜ける。その後正常終了する。

プロセスの実行タイミングと、 fifo の fd の open/close のタイミングの問題な気がしてきています。