箱のプログラミング日記。

えんじにあ奮闘記

RubyでFile.openを使うときに気をつけたいこと

f:id:y_hakoiri:20191102121842j:plain

タイトル通り。

これまであまりファイルの扱いをするような実装をしてこなかったのでメモ。

  • Ruby...2.4.3
  • CSV...2.4.8

File.open

ファイルアクセスのためのクラス。openに引数を渡すことでファイルオブジェクトを生成する。

File.openFile.newで何が違うんだろうと思ったのだけどおそらく挙動は一緒っぽい。

With no associated block, File.open is a synonym for File.new.

ブロックでopenを使う場合は自動でcloseされるのでそこの違い。

class File - Documentation for Ruby 2.4.0

class File (Ruby 2.4.0 リファレンスマニュアル)

File.openを使うときに気をつけたいこと

自分的に気をつけなきゃなーと思ったこと

  • ブロックで使用する
  • テキストかバイナリかをちゃんと指定する
  • エンコードまわりの例外対策をちゃんとしておく

ブロックで使用する

これはもうお決まりというかいろんな書籍でも書いてあることなのだけどうっかりブロック使わずcloseもせず開けっぱなしにしちゃってたので、なるほどこのタイミングかと思い。

f = File.open("testfile", "r")

開けっ放しはダメで、

f = File.open("testfile", "r")
f.close

こうでもなく、

f = ''
File.open("testfile", "r") do |file|
  f = file.read
end

これがよい。ブロックの外(前)でfの初期化だけ忘れずに。

テキストかバイナリかをちゃんと指定する

File.openの第二引数ではファイルアクセスのモードを指定する。

大体r(read)かw(write)かは忘れないと思うけど、ちゃんとテキストかバイナリかも指定するクセをつけようと思いました。

読み込みモードで開く

f = File.open("testfile", "r")

読み込みモードでテキストファイルとして開く

f = File.open("testfile", "rt")

この後エンコードしたりする時に改行文字が含まれているとめんどくさいことになりやすいので、ちゃんとテキストファイルとして読み込んであげるのが良さそう。

エンコードまわりをちゃんとしておく

当たり前っちゃ当たり前なのだけど、予期せぬ文字コードのファイルを開こうとした時にエラーになったり文字化けしたりしないようちゃんと書いておく。

f = File.open("testfile", "rt:Shift_JIS:UTF-8", invalid: :replace, undef: :replace, replace: '?')
  • :invalid => :replace...変換元のエンコーディングにおいて不正なバイトがあった場合に、不正なバイトを置換文字で置き換える
  • :undef => :replace...変換先のエンコーディングにおいて文字が定義されていない場合に、未定義文字を置換文字で置き換える
  • :replace => string...:invalid => :replace:undef => :replaceで用いられる置換文字を指定。デフォルトは Unicode 系のエンコーディングならば U+FFFD、それ以外では "?" を使用。

または、

content = File.read("testfile")
character_code = NKF.guess(content).to_s
f = File.open("testfile", "rt:#{character_code}:UTF-8", invalid: :replace, undef: :replace, replace: '?')

など。

File.readするとメモリを消費してしまうのでデータ量の多いファイルを扱う場合はどうなんだろうか...と思いつつ何かいい案があれば教えてください。。

おまけ:CSVクラスでのencodeオプション

エンコードまわりのオプションを使いたくて、CSVファイルを扱うCSVクラスでもなんとなく同じことができるかなーと思って試したけどできなかった。

File.openで使えるinvalid: :replaceとかundef: :replaceとかは使えなくてちょっとハマりました。

CSV.open("testfile", undef: :replace, invalid: :replace, replace: '?') do |row|
 p row
end
=> ArgumentError: Unknown options:  undef, invalid, replace.

なのでCSVで上記のオプションを使いつつファイルを扱いたいときは、File.openの中でCSVの処理をおこなう。

File.open("testfile", "rt:Shift_JIS:UTF-8", invalid: :replace, undef: :replace, replace: '?') do |file|
  CSV.parse(file) do |row|
    p row
  end
end

でOK。

で、たとえばencodingはどちらでも使えるのに、なぜoptionsはFileクラスでは使えてCSVクラスでは使えないんだろう、と思って少し調べてみたものの

  • File.openはI/O.openのように振る舞う
  • I/O.openのoptionsはString#encodeに対応している
  • String#encodeinvalidundefのようなオプションキーが使用できる

というところまでしかわからなかった。

...と思ったのだけど、どうやらCSV3.1.6以降は使えるようになっているらしい。

不正なバイトを置換文字で置き換えるCSV.openオプション - koicの日記

素敵〜。

というか組み込みライブラリのバージョンの調べ方知らないので調べてみたところ

> p CSV::VERSION
"2.4.8"
=> "2.4.8"

ほえ〜。

参考

class File (Ruby 2.4.0 リファレンスマニュアル)

class CSV (Ruby 2.4.0 リファレンスマニュアル)

class IO - Documentation for Ruby 2.4.0

String#encode (Ruby 2.4.0 リファレンスマニュアル)

不正なバイトを置換文字で置き換えるCSV.openオプション - koicの日記