今日これで数時間潰してしまったのでメモ。
Ruby 1.9のCSVでShift-JISのファイルを読み込むときのバグです。
[ruby]
# 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
[/ruby]
ファイル”test.csv”は以下のもので、行末は”n”でエンコーディングはShiftJISで保存してあります。
[text]
今日は,2月末なのに,東京でも,大雪でした
[/text]
ruby 1.9.2-p290で実行すると
[text]
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 =~': invalid byte sequence in UTF-8 (ArgumentError)
init_separators’
from /Users/nao/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/csv.rb:2027:in
from /Users/nao/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/csv.rb:1570:in initialize'
new’
from /Users/nao/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/csv.rb:1335:in
from /Users/nao/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/csv.rb:1335:in open'
foreach’
from /Users/nao/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/csv.rb:1201:in
from test.rb:19:in `’
[/text]
のようになります。最初の”row_sep”を指定したものはうまくいきますが、”row_sep”を指定していない方はエラーが出ます。
理由は”row_sep”で行末記号を指定してあげないと、CSVは自分で先読みをして行末記号を推定しようとしますが、この処理がエンコーディングによってはバグを起こすようです。
解決策は最初の例のように行末記号をしてあげる、そもそも自動推定をさせないこと。あるいはCSVにtranscodeさせるのをやめ、CSVから得られた個々の結果を個別に#encodeしてあげること(下例)。
[ruby]
CSV.foreach(‘test.csv’, encoding: “SJIS”) do |row|
puts row.map{|c| c.encode(‘UTF-8’)}.join(‘:’)
end
[/ruby]
あるいはRuby 1.9.3ではこのバグは修正されているようですので、1.9.3にアップグレードすれば問題なく処理されます。