リダイレクトの検証などで標準エラー出力が欲しくなったとき、/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