Posted at

シェルスクリプト内で/dev/stderrにリダイレクトしてはいけない

More than 3 years have passed since last update.

リダイレクトの検証などで標準エラー出力が欲しくなったとき、/dev/stderrへのリダイレクトを行うシェルスクリプトを良く書いていましたが、問題があることが分かったのでまとめておきます。


ダメな例


output.sh

#!/bin/sh

echo 'stdout1' > /dev/stdout
echo 'stdout2' > /dev/stdout
echo 'stderr1' > /dev/stderr
echo 'stderr2' > /dev/stderr



テストすると正常に動いているように見える

$ ./output.sh

stdout1
stdout2
stderr1
stderr2
$ ./output.sh > /dev/null # 標準出力を抑制
stderr1
stderr2
$ ./output.sh 2> /dev/null # 標準エラー出力を抑制
stdout1
stdout2


しかし、ファイルにリダイレクトすると問題が起こる

$ ./output.sh > output.log

stderr1
stderr2
$ cat output.log
stdout2 # stdout1が消えてしまっている

$ ./output.sh >> output.log # 追記してみるが

stderr1
stderr2
$ cat output.log
stdout2 # やはり最後の出力しか残らない


良い例


output.sh

#!/bin/sh

echo 'stdout1'
echo 'stdout2' >&1 # 意味はないが表記を揃えたければ書いてもOK
echo 'stderr1' >&2
echo 'stderr2' >&2



問題なく動作する

$ ./output.sh > output.log

stderr1
stderr2
$ cat output.log
stdout1
stdout2

$ ./output.sh 2> output.log

stdout1
stdout2
$ cat output.log
stderr1
stderr2


原因

色々考えてみたらある程度は説明がついたが、正確な所は良くわからなかった。

以下のように書き換えると正常に動作するので、echo hoge > /dev/stdXXXの度にoutput.logがクリアされているようだ。もちろん、こんな書き方をするよりは「良い例」に書いた方法を使う方が良いね。


output.sh

#!/bin/sh

echo 'stdout1' >> /dev/stdout
echo 'stdout2' >> /dev/stdout
echo 'stderr1' >> /dev/stderr
echo 'stderr2' >> /dev/stderr