はじめに
Ruby初学者のため、File.open()
の挙動が知りたくなるシチュエーションに遭遇。
調べたら2009年の記事を発見。こちらの記事でFileOpenの動作確認をしていたので、コードを自分でも実際に動かしてみた。パターン網羅的で参考にしやすい。
RubyのFile.openのブロックによるリソース管理について - OSのようなもの -
http://d.hatena.ne.jp/wocota/20090426/1240750685
使わせてもらうコードはこちら
f1 = f2 = f3 = f4 = f5 = '' #open 1 File.open('test.txt'){|f1| puts f1.read #open 2 f2 = File.open('test2.txt') puts f2.read #open 3 f3 = File.open('test3.txt').each do |l| puts l end #open 4 (f4 = File.open('test4.txt').each do |l| puts l end).close #open 5 File.open('test5.txt'){|f5| f5.read }.each{|l| puts l } } p f1 #=> #<File:test.txt (closed)> p f2 #=> #<File:test2.txt> p f3 #=> #<File:test3.txt> p f4 #=> #<File:test4.txt (closed)> p f5 #=> #<File:test5.txt (closed)>
実行失敗
メソッドチェーンしてる5番目のファイルで実行エラー。
$ ruby file_open.rb
test1です
test2です
test3です
test4です
f.rb:17:in `block in <main>': undefined method `each' for "test5です\n":String (NoMethodError)
from f.rb:3:in `open'
from f.rb:3:in `<main>'
修正と再実行
Ruby 1.9 Ruby 1.9では、each_lineメソッドの別名eachが廃止されました。
Stringオブジェクトに対するeach
メソッドは廃止されているため、each_line
あたりで代替して動かす。
- 修正後ソース(抜粋)
File.open('test5.txt'){|f5|
f5.read
}.each_line{|l|
puts l
}
- 再実行結果(抜粋)
""
#<File:test2.txt>
#<File:test3.txt>
#<File:test4.txt (closed)>
""
Rubyのバージョン違いのせいか、f1
とf5
の結果が 参照元 と一致しない。
仮に参照元と同じ動作をするならば、期待としてはファイルオブジェクトが返ってきてほしい。
ブロック内のf1
とf5
は変数のスコープ的にグローバル変数とは別物として扱われ、ファイルオブジェクト(File)は返ってこない。そのため表示されているf1
とf5
は初期化時の空文字(String)が表示される。
結果
参照元のコードだとブロックを使ったときのファイルクローズが目視できなかった。
ただ、.read
を使っていることからFile.open('test.txt'){ |f5| f5.read }
の戻り値はString
であり、ブロック処理終了後にファイル操作が完結していることが伺える。
Rubyバージョン
2.3.1
と2.4.1
で実行
$ ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin16]
$ ruby -v
ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-darwin16]