LoginSignup
46
41

More than 5 years have passed since last update.

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

Posted at

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
46
41