「Rubyベストプラクティス」を読む4


4章はファイルやテキスト処理、正規表現をあつかっています。

目次:4章 テキスト処理とファイル管理

  • 4.1 状態トラッキングによる行指向のファイル処理
  • 4.2 正規表現
  • 4.3 ファイルを扱う
  • 4.4 tempfile標準ライブラリ
  • 4.5 テキスト処理戦略
  • 4.6 まとめ

4.1 状態トラッキングによる行指向のファイル処理

テキスト処理について、パーサーをつくる一つの方法が紹介されています。

# 本に載っているパーサーのコードを一部簡単にして書く
def parse(file_name)
  # 状態を保持する配列
  section = []
  
  File.foreach(file_name) do |line|
    # 状態を変更する処理
    # 状態はスタックで管理する
    case line
    when /^Start(\w+)/
      section.push $1
      next
    when /^End(\w+)/
      section.pop
      next
    end

    # 現在の状態で行う処理を決める
    case section
    when ["Font", "Metrics"]
      parse_char(line)
    when ["Kern"]
      parse_kern(line)
    when ["Metric"]
      next  # 処理しなくてもよいときは、nextで飛ばす
    else
      parse_generic(line) # その他一般の処理
    end
  end
end

4.2 正規表現

アンカーや量指定子の使い方が解説されている。

4.3 ファイルを扱う

テンポラリファイルを使う必要があるときは、自作するよりtempfileライブラリを使う。

require 'tempfile'
temp = Tempfile.new('foo.txt')
temp << some_data

# 読み込むときは、rewindで巻き戻す
temp.rewind
temp.each do |line|
  # 何かする
end

temp.close
# テンポラリファイルをすぐに削除したいときは、Tempfile#close! を使う。
# temp.close!

# テンポラリディレクトリを指定することもできる
a = Tempfile.new("foo.txt", "path/to/tmpdir")


File.foreachはEnumeratorを返すので、テキスト処理にEnumerableのメソッドが使える

sum = 0
File.foreach("data.txt"){|line| sum += line[/total: (\d+)/, 1].to_f}

# Enumeratorを使った処理
enum = File.foreach("data.txt")
sum = enum.inject(0){|s, r| s + r[/total: (\d+)/, 1].to_f}


ファイル処理をしていて、現在の行数を知りたいときは、File#linenoを使う

keys = []
values = []
File.open("foo.txt") do |file|
  file.each do |line|
    (file.lineno.odd? ? keys : values) << line.chomp
  end
end

Hash[*keys.zip(values).flatten]


ファイルに修正を施して保存する場合、"r+"でファイルを開いて処理するよりも、テンポラリファイルに処理結果を書いておいて、最後にリネームするやり方のほうが簡単。

require "tempfile"
require "fileutils"

file_name = "data.txt"
temp = Tempfile.new("working")
File.foreach(file_name) do |line|
  # 何かする
  temp << result
end

temp.close
# 安全のため、ファイルをバックアップしておく
FileUtils.cp(file_name, "#{file_name}.bak")
FileUtils.mv(temp.path, file_name)