From: Koji Arai Date: 2002-02-22T02:39:51+09:00 Subject: [ruby-dev:16030] Re: $stdin/out/err への代入 新井です。 私が意見を書いても [ruby-dev:14601] のやきなおしになるだけで はありますが。 In message "[ruby-dev:15927] $stdin/out/err への代入" on Mon, 18 Feb 2002 14:24:53 +0900, matz@ruby-lang.org (Yukihiro Matsumoto) wrote: > > In message "[ruby-dev:15909] 1.6 の寿命 (Re: Re: ERb)" > on 02/02/18, Koji Arai writes: > > |* while の戻り値 > > こっちは「いつもnil」ということにしましょう。 > > |* $stdout, $stderr 等への代入 > | > | 1.6 の挙動が良いのではないかと思います。 > > せっかくなんで問題をまとめておきましょう。 > > 問題 > > $stdin, $stdout, $stderrへの代入はCのstdin, stdout, stderrを > 取り替える動きが期待されてると思われる。それをどう実現するか。 > 特に > > * printfのような内部的にstdioとやりとりをするもの > * fork/execしたサブプロセす > > からのアクセスをどう制御するか > > 案1 (Ruby1.6) > > 代入後、$stdin=>0, $stdout=>1, $stderr=>2 にdup2(2)する。た > だし、右辺がT_FILEでなければそのまま代入する。 > > * あらかじめ明示的にdupするなどしなければ、標準のstdinなど > を取り返す方法がない 逆に言うと、戻したければ dup を保持しておけばよく。 いつも元に戻したい状況が発生するわけではない。 ドキュメントにその旨さえ書いておけばあんまり問題ない。 # $stdout にオブジェクトを代入すると共に # 元の IO を代入されたオブジェクトにリダイレクトする # 最初の状態 p $stdout.id # => 537767998 (画面出力) p $stdout.fileno # => 1 (画面出力) # 後で元に戻すために dup を保持しておく sv = STDOUT.dup # リダイレクトする $stdout = File.open('/tmp/file', 'w') # 代入で別オブジェクトを指している # さらに p の出力先(STDOUT)がリダイレクトされている p $stdout.id # => 537759058 (/tmp/file の内容) p $stdout.fileno # => 5 (/tmp/file の内容) # 元に戻す(元の出力先を保持した(dupした)IOに # リダイレクトし直す) $stdout = sv p $stdout.id # => 537759208 (画面出力) p $stdout.fileno # => 4 (画面出力) # ただし(完全に$stdout等が指すオブジェクトまで)元に戻すには # 以下も行う必要がある。そいういう要求の元ではこの一連の手順 # は面倒だし一見変なので問題ありかもしれない。 # ここでは$stdout と STDOUT は同じ出力先を指すので # ただの代入と同じ $stdout = STDOUT p $stdout.id # => 537767998 (画面出力) p $stdout.fileno # => 1 (画面出力) 説明があると長く見えるので上記の手順を簡単に書き直すと p $stdout.id # => 537767998 (画面出力) sv = $stdout.dup $stdout = File.open('/tmp/file', 'w') p $stdout.id # ファイル出力 $stdout = sv $stdout = STDOUT p $stdout.id # => 537767998 (画面出力) といったところ。 > 案2 (Ruby 1.7.2) > > 代入後、 $stdin=>0, $stdout=>1, $stderr=>2 にdup2(2)する。代 > 入前にsaved_fd配列に元のstdioのfd(0,1,2)をdup(2)して保存して > おく。$stdin/out/errの初期値を再代入するとsaved_fdから復旧。 > > * $stdin = STDINでもとのstdinを取り戻せる つまり、案1の元に戻す面倒臭い手順を Ruby が内部で行ってくれる # 最初の状態 p $stdout.id # => 537767998 (画面出力) p $stdout.fileno # => 1 (画面出力) # リダイレクトする $stdout = File.open('/tmp/file', 'w') # 代入で別オブジェクトを指している # さらに p の出力先(STDOUT)がリダイレクトされている p $stdout.id # => 537759058 (/tmp/file の内容) p $stdout.fileno # => 5 (/tmp/file の内容) # 元に戻す(元の fd と同じ fd を持つオブジェクトが # 代入されるとリダイレクト先も元に戻る) $stdout = STDOUT p $stdout.id # => 537766888 (画面出力) p $stdout.fileno # => 1 (画面出力) 簡単に書き直すとこう p $stdout.id # => 537767998 (画面出力) $stdout = File.open('/tmp/file', 'w') p $stdout.id # ファイル出力 $stdout = STDOUT p $stdout.id # => 537767998 (画面出力) 一見、単純ですがモデル的には変です。最後の元に戻す操作 なんて、代入する値(STDOUT)が変更(リダイレクト)されてます。 また、二回代入すると元の状態に戻らない。これはバグなのかもし れない。 p "#1" p $stdout.id # => 537766888 (画面出力) p $stdout.fileno # => 1 (画面出力) $stdout = File.open('/tmp/foo', 'w') p "#2" p $stdout.id # => 537756828 (/tmp/foo の内容) p $stdout.fileno # => 4 (/tmp/foo の内容) $stdout = File.open('/tmp/bar', 'w') p "#3" p $stdout.id # => 537756298 (/tmp/bar の内容) p $stdout.fileno # => 6 (/tmp/bar の内容) $stdout = STDOUT p "#4" p $stdout.id # => 537766888 (/tmp/foo の内容) p $stdout.fileno # => 1 (/tmp/foo の内容) > 案3 > > なにもしない。$stdinなどへの代入はRubyの外ではなんの影響もな > いということにする。printfなども同様... > > * さすがに受け入れられないかも。 これも検討してくれるなら私は大歓迎しますが(^^。 これをする場合、$defout は、$stdout の alias にしましょう。 案1も案2も代入することですべての標準入出力が変わるので見た目 期待通り、単純な挙動、ユーザにやさしい、ように見えるけど実は 複雑なことをしている。裏で複雑なことをするだけなら問題ないけ どそれを隠蔽しきれてないのが問題。 代入に副作用があるというのは、それだけで期待通りの挙動ではな い場合が多い。 たぶん、$stdout への代入はあまり使われてないのではないかと予 想します(勝手だ --;)。 子プロセスや直接 fd を触るライブラリ等に影響を及ぼしたい場合 は reopen() するとか手はある。 案4 話を複雑にしますが、[ruby-dev:14601] にあげた案として sv = STDOUT.dup $stdout = File.open('/tmp/file', 'w') p $stdout.fileno # => 1 (/tmp/file の内容) $stdout = sv p $stdout.fileno # => 1 (画面出力) というのが、あってこれは $stdout 等への代入に代入の意味を 持たせないというものです。(つまり、これらへの代入はdup2) まあ、さすがに代入が代入でないというのはダメダメなので 案5 案4 のかわりに 案3 + IO#dup2 メソッドの追加(名前がアレです が)でも良い(その方が良い)かな?。 IO#redirect(fd) とか。 結論として、現状維持の案1か、理想は案5(の前半)が良いように思 います。案5をするなら 1.8 は、案1で将来案5になることをアナウ ンスしておくのが良いと思います。 ちょっとずれますが、(ここでは reopen について言及しますが) sv = $stdout.dup p $stdout.fileno p $stdout.reopen("/tmp/baz", "w").fileno p $stdout.reopen(sv).fileno と同等な p $stdout.fileno $stdout.reopen("/tmp/baz", "w") {|io| # io.equal?($stdout) p io.fileno # => 1 (/tmp/baz へ出力) } p $stdout.fileno # => 1 (画面出力) とかあっても良いかと。スレッドのことを考えるとアレですが、ま あこれは避けられないと思うので。これ見てやっぱり $stdout は 代入とリダイレクトが一緒に作用するのが良くないんだと言うこと がよくわかる(メソッドなら戻す手順も単純)。 -- 新井康司 (Koji Arai)