RubyでShiftJISのファイルを扱う(1.9.3, 2.0系対応版)

morimorihogeです.更新が不定期ですみません.最近夕立が改二になりましたが3-4が越せません.(*1

普段Ruby/Railsを使っていると,基本的に文字コードはUTF-8を使っているかと思います.Rubyは割と文字コードについてはバージョン毎に扱いが変わっており,以下の様な挙動になります.

  • 1.8.7以前: デフォルトUS-ASCII,日本語を扱う際は$KCODEの指定を明示的に呼び出す
  • 1.9.3以前: デフォルトUS-ASCII,日本語を扱う際はcoding: utf-8等のマジックコメントを記述する
  • 2.0.0以降: デフォルトUTF-8

というわけで,ここ最近のプロジェクトでRuby 2.0系を使うのであれば,マジックコメントも何も必要無くUTF-8を使うことができます.
しかし,レガシーシステムとの連携といった理由で他の文字コードのデータを読み書きする必要は依然存在します.本記事では試しにShiftJIS(CP932)のファイルを扱ってみます.動作確認はRuby 2.0.0p247で行っていますが,恐らく1.9.3系でも問題なく動作するかと思います.

非UTF-8ファイルの読み込み(文字列編)

UTF-8以外のファイルを文字列として読み込むときには,

File.read("hoge.txt", encoding: 'cp932')

とかやれば良いです.読み込み時に自動的にUTF-8に変換されて,その後は普通の文字列として扱えます.普通ならこれで良いですね.

非UTF-8ファイルの読み込み(バイナリ編)

さて,次はもうちょっと厄介なファイルを想定してみます.具体的にはファイルの区切り文字などがなく,バイト位置でデータ範囲が指定されている様な固定長データのケース(旧世代のCOBOLで書かれたえんたーぷらいずなシステムとかが好みそうな奴)を想定します.
このようなケースでは「XXバイト目からYYバイトはASDFというデータ」という固定長データを扱うことが多いです.今回処理する必要になったデータは,100以上のデータが毎行CRLFで区切られており,データの中にはSJIS以外の文字コードも許可されているというアレゲなファイルでした.

文字列がSJISとは限らないという問題の他に,データをバイト位置指定で読み込む必要があるため,今回は文字コード指定して読み込むのではなく,バイナリで読み込むことにしました.
Ruby 1.9.3以降でバイナリとしてファイルを読み込むにはFile.binreadを使い,以下の様に読み込みます.

binary_text = File.binread("hoge.txt")

その後,区切り文字はCRLFと分かっているので,splitにかけて1行ずつに分解,その後以下のTokenizerにかけてbytesliceにかけてparseしました.Ruby 1.9以降ではHashはordered hashになるので,FORMAT_HASHの順序には意味があります.また,bytesliceはRuby 1.9.3以降で使える様なので,古いRubyでは使えないことに注意して下さい.
とりあえず適当に読み込んでUTF-8変換して表示してみました.表示するときにはencodeメソッドで明示的に文字コード変換してやらないと文字化けるので注意です.

class HogeTokenizer
  // 項目 => バイト数のハッシュ
  FORMAT_HASH = {
    '作成日'       => 8,
    '会員番号'   => 8,
    '会員ほげほげ区分'   => 3,
    '会員ふがふが区分'  => 1,
    '会員謎コード'     => 11,
    // ..... 仕様書に従って定義を書く
  }.freeze

  class << self
    def parse_data(data)
      current = 0
      hash    = {}
      FORMAT_HASH.each do |key, length|
        hash[key] = data.byteslice(current, length)
        current   += length
      end
      hash
    end
  end
end

binary_text = File.binread("hoge.txt")
data_array = binary_text.split("\r\n")
data_array.each do |data|
  hash = HogeTokenizer::parse_data(data)
  hash.each do |key, val|
    puts "  #{key}: #{val.encode('utf-8', 'cp932')}"
  end
end

まとめ

というわけで,RubyでShiftJIS(CP932)のファイルを扱う方法をまとめてみました.普通はFile.readで済むのですが,稀にバイト列で扱いたいケースがあるので,そういった場合には参考になるかなと思います.

ではでは.

*1) 初稿時夕立改としていましたが,夕立改二の間違いです.lvl55は別府の70よりは楽でしたが3-2-1回すのはそろそろ苦痛になってきました

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

morimorihoge

高校卒業後,学生をやりながらずっとWebアプリ開発に携わってきました.2010くらいまではPHP/Symfonyプログラマでしたが,それ以降のWeb開発はRailsほぼ一本に宗旨替えしました.開発とは別にサーバ構築・運用も10年以上やってきているので,要件定義から設計・実装・環境構築・運用まで一通り何でもこなせます.開発以外では季節により大学でWebサービス開発やプログラミング関連の非常勤講師もしており,技術の啓蒙・教育にも積極的に関わっています.最近はPM的な仕事が増えていますが,現役開発者としていつでも動ける程度にはコードもサーバも弄る日々を送っています.AWS 認定ソリューションアーキテクト – アソシエイトレベル取りました

morimorihogeの書いた記事

開発
RubyのArray(配列)の使い方

2017年03月15日

週刊Railsウォッチ

インフラ

Rubyスタイルガイドを読む

BigBinary記事より

ActiveSupport探訪シリーズ