Ruby
Ruby2.3
Ruby2.4

【実際に動かしてみた】RubyのFile.openのブロックによるリソース管理について

はじめに

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のバージョン違いのせいか、f1f5の結果が 参照元 と一致しない。

仮に参照元と同じ動作をするならば、期待としてはファイルオブジェクトが返ってきてほしい。

ブロック内のf1f5は変数のスコープ的にグローバル変数とは別物として扱われ、ファイルオブジェクト(File)は返ってこない。そのため表示されているf1f5は初期化時の空文字(String)が表示される。

結果

参照元のコードだとブロックを使ったときのファイルクローズが目視できなかった。
ただ、.readを使っていることからFile.open('test.txt'){ |f5| f5.read }の戻り値はStringであり、ブロック処理終了後にファイル操作が完結していることが伺える。

Rubyバージョン

2.3.12.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]

参考文献