【Ruby】よく使うFileクラスを使ったファイル読み込み処理


Fileクラスを使ったファイルの読み込み処理のうち、個人的に良く使うパターンをテンプレコードとしてまとめた。

### 前提
- ファイル読み込みでは、実際には読み込んだあとに何らかの処理をすることが多いと思うが、ここではputsで出力するだけ
- 例外発生時は、捕捉した例外クラスとメッセージを出力するだけにする
- カレントディレクトリにlabmen.txtという名前で以下の内容のファイルがあるものとする。

```txt:
Okabe Rintaro
Shiina Mayuri
Hashida Itaru
Makise Kurisu
Kiryu Moeka
Urushibara Ruka
Feyris
Amane Suzuha
```

### 1行ずつ読み込む

**IO#each_lineを使うパターン**

```ruby:
begin
  # File.openはファイルをオープンし、Fileオブジェクトを返す
  # 第1引数: ファイルパス
  # 第2引数: ファイルモード (デフォルト => 'r')
  # 第3引数: ファイルを生成する場合のパーミッション(デフォルト => 0666)
  # 失敗した場合にErrno::EXXX例外が発生
  #
  # File.openにブロックを渡すと、
  # ブロックが終了した時点でファイルを自動でクローズする
  File.open('labmen.txt') do |file|
    # IO#each_lineは1行ずつ文字列として読み込み、それを引数にブロックを実行する
    # 第1引数: 行の区切り文字列
    # 第2引数: 最大の読み込みバイト数
    # 読み込み用にオープンされていない場合にIOError
    file.each_line do |labmen|
      # labmenには読み込んだ行が含まれる
      puts labmen
    end
  end
# 例外は小さい単位で捕捉する
rescue SystemCallError => e
  puts %Q(class=[#{e.class}] message=[#{e.message}])
rescue IOError => e
  puts %Q(class=[#{e.class}] message=[#{e.message}])
end
```

実行結果

```
Okabe Rintaro
Shiina Mayuri
Hashida Itaru
Makise Kurisu
Kiryu Moeka
Urushibara Ruka
Feyris
Amane Suzuha
```

**IO.foreachを使うパターン**

```ruby:
begin
  # IO.foreachはファイルの各行を引数としてブロックを繰り返し実行する
  # 第1引数: ファイルパス
  # 第2引数: 行の区切り文字 (デフォルト => $/)
  # オープンに失敗した場合、Errno::EXXX例外が発生
  File.foreach('labmen.txt') do |labmen|
    # labmenには読み込んだ行が含まれる
    puts labmen
  end
# 例外は小さい単位で捕捉する
rescue SystemCallError => e
  puts %Q(class=[#{e.class}] message=[#{e.message}])
end
```

実行結果

```
Okabe Rintaro
Shiina Mayuri
Hashida Itaru
Makise Kurisu
Kiryu Moeka
Urushibara Ruka
Feyris
Amane Suzuha
```

### 1度に全体を読み込む

**IO#readを使う**

```ruby:
begin
  File.open('labmen.txt') do |file|
    # まずIO#readでファイル全体を文字列として読み込む
    # 次にString#splitで改行文字ごとに配列に変換
    # 最後にArray#eachで要素ごとにブロックを評価
    #
    # IO#read
    # 第1引数: 読み込むサイズ(デフォルト => nil)
    # 第2引数: 出力用のバッファ(デフォルト => '')
    # 読み込み用にオープンされていない場合にIOErrorが発生
    # データの読み込みに失敗した場合にErrno::EXXXが発生
    file.read.split("\n").each do |labmen|
      puts labmen
    end
  end
# 例外は小さい単位で捕捉する
rescue SystemCallError => e
  puts %Q(class=[#{e.class}] message=[#{e.message}])
rescue IOError => e
  puts %Q(class=[#{e.class}] message=[#{e.message}])
end
```

実行結果

```
Okabe Rintaro
Shiina Mayuri
Hashida Itaru
Makise Kurisu
Kiryu Moeka
Urushibara Ruka
Feyris
Amane Suzuha
```

### 1文字ずつ読み込む
**IO#each_charを使う**

```ruby:
begin
  # File.openはファイルをオープンし、Fileオブジェクトを返す
  # 第1引数: ファイルパス
  # 第2引数: ファイルモード (デフォルト => 'r')
  # 第3引数: ファイルを生成する場合のパーミッション(デフォルト => 0666)
  # 失敗した場合にErrno::EXXX例外が発生
  #
  # File.openにブロックを渡すと、
  # ブロックが終了した時点でファイルを自動でクローズする
  File.open('labmen.txt') do |file|
    # IO#each_charは文字を1つずつブロックに渡して評価する
    # 読み込み用にオープンされていない場合にIOErrorが発生する
    file.each_char do |char|
      # 改行文字以外の場合、文字の後ろにスペースを入れて出力する
      if char != "\n"
        print "#{char} "
      else
        print char
      end
    end
  end
# 例外は小さい単位で捕捉する
rescue SystemCallError => e
  puts %Q(class=[#{e.class}] message=[#{e.message}])
rescue IOError => e
  puts %Q(class=[#{e.class}] message=[#{e.message}])
end
```

実行結果

```
O k a b e   R i n t a r o
S h i i n a   M a y u r i
H a s h i d a   I t a r u
M a k i s e   K u r i s u
K i r y u   M o e k a
U r u s h i b a r a   R u k a
F e y r i s
A m a n e   S u z u h a
```

### ファイルロックして読み込む
**File#flockを使う**

```ruby:
begin
  # File.openはファイルをオープンし、Fileオブジェクトを返す
  # 第1引数: ファイルパス
  # 第2引数: ファイルモード (デフォルト => 'r')
  # 第3引数: ファイルを生成する場合のパーミッション(デフォルト => 0666)
  # 失敗した場合にErrno::EXXX例外が発生
  #
  # File.openにブロックを渡すと、
  # ブロックが終了した時点でファイルを自動でクローズする
  File.open('labmen.txt') do |file|
    # File#flockでファイルをロックする
    # 第1引数: ロックの種類をFile::Constantsの定数で指定（File::LOCK_EXは排他ロックを意味する）
    # 自身がcloseされている場合にIOErrorが発生
    # 引数に不正な整数を与えた場合などにErrno::EXXX例外が発生
    #
    # アンロックはファイルが閉じられるときに自動で行われる
    file.flock File::LOCK_EX

    file.each_line do |labmen|
      puts labmen
    end
  end
# 例外は小さい単位で捕捉する
rescue SystemCallError => e
  puts %Q(class=[#{e.class}] message=[#{e.message}])
rescue IOError => e
  puts %Q(class=[#{e.class}] message=[#{e.message}])
end
```

実行結果

```
Okabe Rintaro
Shiina Mayuri
Hashida Itaru
Makise Kurisu
Kiryu Moeka
Urushibara Ruka
Feyris
Amane Suzuha
```

ちなみに、排他ロックされたファイルを別プロセスで排他ロックしようとすると、処理が一時停止してロックが解除されたタイミングで後続の処理が走る。