ちょっと前の Ruby-dev office hour で STDOUT
と $stdout
について話題になってたので書いてみる。
発端は Ractor で $stdout
は使えるけど STDOUT
は使えないというものだったようだけど、まあ Ractor についてはよくわからないんで置いておく。
Ruby には標準入出力エラー出力を表すものとして、定数(STDIN
, STDOUT
, STDERR
)とグローバル変数($stdin
, $stdout
, $stderr
)がある。
以下で STDOUT
/ $stdout
と書いてるけど、STDIN
/ $stdin
, STDERR
/ $stderr
でも同じ。
Ruby プロセス起動直後は同じオブジェクトを指している。
STDOUT.__id__ # => 7984 $stdout.__id__ # => 7984
$stdout
は変数なので代入できる。そうすると当然異なるオブジェクトになる。
$stdout = File.open('/tmp/hoge.out', 'a') $stdout == STDOUT #=> false
Ruby の p
や puts
は $stdout
に出力するので、$stdout
に IO オブジェクトを設定するだけでリダイレクトのようなことができる。
なので普通は $stdout
を使えばいい。
じゃあ STDOUT
は何のためにあるのか?
定数なので代入できない(いや Ruby なのでできちゃうんだけど流石にそんなことやる人はいないだろう)。
つまり $stdout
を代入して変更したとしても STDOUT
はファイル記述子1番が維持されてる(同様に STDIN
は 0番、STDERR
は 2番が維持されている)。
$stdout = File.open('/tmp/hoge.out', 'a') $stdout.fileno #=> 5 STDOUT.fileno #=> 1
なので、$stdout
を元に戻すのに使える。
$stdout = STDOUT
ところで、Ruby プログラムから別のプログラムを起動するときには $stdout
は引き継がれない。
これ↓を実行すると ls
の出力は hoge.out
には出ずに端末に表示される。ファイル記述子1は変わってないので。
$stdout = File.open('/tmp/hoge.out', 'a') system('ls')
リダイレクトを別プログラムに引き継ぎたい場合はファイル記述子1に対応している STDOUT
を変える必要がある。
昔はこんな感じで書いたりしてた:
pid = fork do STDOUT.reopen('/tmp/hoge.out', 'a') exec('ls') end Process.wait(pid)
普通は $stdout.reopen
でもいいのだけども、$stdout
は他で変更されてる可能性があるので、STDOUT
の方が無難。
でも現代の Ruby は fork
& exec
なんてしなくても system
のオプションでリダイレクトを指定できるので簡単にできちゃう:
system('ls', out: ['/tmp/hoge.out', 'a'])
なので、今はもうあんまり定数の方を使う場面はないのかもしれない。
噂によると「プログラム中でグローバル変数は使うべきじゃない」というのに惑わされて $stdout
は絶対使いたくないという人もいるみたいだけど、プログラムの中でグローバルなオブジェクトなんだからグローバル変数を使うのは理にかなってるんで気にせずに使おう。