Ruby 1.9.2 CSV でInternal Encodingを指定するとおかしくなる件

今日これで数時間潰してしまったのでメモ。

Ruby 1.9のCSVでShift-JISのファイルを読み込むときのバグです。

# encoding: UTF-8

require 'csv'

CSV.foreach('test.csv', row_sep: "n", encoding: "SJIS:UTF-8") do |row|
  puts row.join(':')
end

CSV.foreach('test.csv', encoding: "SJIS:UTF-8") do |row|
  puts row.join(':')
end

ファイル”test.csv”は以下のもので、行末は”n”でエンコーディングはShiftJISで保存してあります。

今日は,2月末なのに,東京でも,大雪でした

ruby 1.9.2-p290で実行すると

NaoAir:Desktop nao$ ruby test.rb 
今日は:2月末なのに:東京でも:大雪でした
/Users/nao/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/csv.rb:2027:in </code><code>=~': invalid byte sequence in UTF-8 (ArgumentError)
	from /Users/nao/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/csv.rb:2027:in </code>init_separators'
	from /Users/nao/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/csv.rb:1570:in <code>initialize'
	from /Users/nao/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/csv.rb:1335:in </code>new'
	from /Users/nao/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/csv.rb:1335:in <code>open'
	from /Users/nao/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/csv.rb:1201:in </code>foreach'
	from test.rb:19:in `'

のようになります。最初の”row_sep”を指定したものはうまくいきますが、”row_sep”を指定していない方はエラーが出ます。

理由は”row_sep”で行末記号を指定してあげないと、CSVは自分で先読みをして行末記号を推定しようとしますが、この処理がエンコーディングによってはバグを起こすようです。

解決策は最初の例のように行末記号をしてあげる、そもそも自動推定をさせないこと。あるいはCSVにtranscodeさせるのをやめ、CSVから得られた個々の結果を個別に#encodeしてあげること(下例)。

CSV.foreach('test.csv', encoding: "SJIS") do |row|
  puts row.map{|c| c.encode('UTF-8')}.join(':')
end

あるいはRuby 1.9.3ではこのバグは修正されているようですので、1.9.3にアップグレードすれば問題なく処理されます。