LoginSignup
4
5

More than 5 years have passed since last update.

ファイル読み込みの際に文字コード関係の例外が発生する場合(File#openの引数にはString#encodeのオプションが利用できる)

Last updated at Posted at 2017-08-10
File.open("sample.rb", "r", :encoding => Encoding::CP932) do |file|
  file.each_line do |line|
    do_something(line)
  end
end

ファイル読み込み処理を行うと、頻繁に出くわすのがエンコード関係の例外。Encoding::InvalidByteSequenceErrorなど、その名もずばりな例外はもちろんのこと、たとえば読み込んだ行に対してline.split(",")を実行したとたん、ArgumentErrorが発生するということもあります。

このような例外はたいていファイルに含まれる不正なバイトやや変換できない文字が原因です。ではそのようなバイトや文字を読み込んだ場合でも、例外を発生させるのではなく、あたかも正しいバイトや文字を読み込んだかのようにふるまわせるにはどうすればよいでしょうか? あけすけにいってしまうと――ファイルの読み込み時に文字コード関係の例外を抑制するにはどうすればよいでしょうか?

実はFile#openの引数として、String#encodeのオプションを指定することができます。これを利用しましょう。たとえば「読み込んだファイルに不正なバイトや変換不可能文字が含まれていた場合でも例外を発生させるのではなく、無視して処理を実行したい」というときには次のように書きます。

File.open("sample.rb", "r", :encoding => Encoding::CP932, :invalid => :replace, :undef => :replace) do |file|
  file.each_line do |line|
    do_something(line)
  end
end

この例では「不正な文字」は置換されるため、例外の発生が抑制されるのですが、一方で情報が失われる可能性があることにも注意する必要があります。ただし「ファイル処理のたびにbegin/rescue/retryを書く面倒さ」「巨大ファイルの処理中に例外が起きたために最初からやり直さねばならない絶望感」そして「困難なデバックの結果、その例外が本質的ではないところで起きたことが分かった時のやるせなさ」などなどを勘案すると、情報を失うことを覚悟で「不正な文字」を捨ててしまうのがもっとも現実的だとは思います。

なお「File#openの引数にString#encodeと同じオプションが指定できる」ことですが、File#openの「るりま」には明記されていません。「るりま」ではIO#openにその旨が少し書かれているだけで、やや不親切なような気もしなくはない(´・ω・`)

またcsvライブラリにもファイルをCSV形式として開くCSV#openが用意されていますが、こちらのメソッドはString#encodeと同じオプションを指定することはできません。同じ名前なのにややこしい(´・ω・`) したがってCSV#openを何も考えずに使っていると、文字コードがらみの例外が頻発することになります。

# この書き方だと「不正な文字」に出くわすと例外が発生する。
require "csv"
CSV.open("sample.csv", "r", :encoding => Encoding::CP932) do |csv|
  csv.each do |row|
    do_something(row)
  end
end

この対処法は実は簡単です。上述の例ではCSV#openの引数にパス文字列を与えていますが、これをFileオブジェクトに変更します。そしてこのFileオブジェクトを生成するFile#openメソッドの引数として、String#encodeのオプションを指定すればよいというわけです。

# 2017-10-24: サンプルコードに誤りがあったため修正
# require "csv"
# File.open("sample.csv", "r", :encoding => Encoding::CP932, :invalid => :replace, :undef => :replace) do |file|
#   CSV.open(file) do |csv|
#     csv.each do |row|
#       do_something(row)
#     end
#   end
# end

require "csv"
File.open("sample.csv", "r", :encoding => Encoding::CP932, :invalid => :replace, :undef => :replace) do |file|
  CSV.parse(file) do |row|
    do_something(row)
  end
end

★参考★
- File#open: https://docs.ruby-lang.org/ja/latest/method/File/s/new.html
- String#encode: https://docs.ruby-lang.org/ja/latest/method/String/i/encode.html
- IO#open: https://docs.ruby-lang.org/ja/latest/method/IO/s/open.html
- CSV: https://docs.ruby-lang.org/ja/latest/class/CSV.html

4
5
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5