Ruby TIPS

Ruby TIPS

ファイルから文字列を読み込む(入力する)には?(基本編)

2017年5月15日

テキストファイルから文字列を読み込むための基礎を解説。ファイル操作をブロックで記述する方法や、ファイルを開く際に「テキスト読み出し専用モード」でアクセスしたり文字コードを指定したりする方法、BOM付きファイルを処理する方法を説明する。

ローグ・インターナショナル 羽山 博
  • このエントリーをはてなブックマークに追加

 Rubyでテキストファイルを開くときには、アクセスモード(読み込み、書き込みなど)やエンコーディング(文字コード)の指定が必要である。また、ファイルから文字列の読み込みや書き込み(=入出力)の際には、全体を読み込むのか、行や文字ごとに読み込むのか、と考慮すべき事柄がたくさんある。全てを一度に学ぶのは大変なので、今回は、読み込み(入力)に絞り、基本的な方法を示す。さらに後日公開予定の「ファイルから1文字ずつ読み込む(入力する)には?」(次回)と「ファイルから1行ごと/段落ごと読み込む(入力する)には?」(次々回)で、読み込み機能に関連するさまざまなメソッドを、順を追って少しずつ見ていく。

ファイル入出力の基本 − 入力編

 ファイルを利用するには、一般的にはFileクラスのopenメソッドを使ってファイルを開き、readメソッドやreadlineメソッドなどを使ってデータを読み込む。これらの入出力メソッドはIOクラスのメソッドなので、ドキュメントを参照する際にはIOクラスも併せて見るようにしよう(FileクラスはIOクラスを継承しているので、それらのメソッドが利用できる)。なお、書き込みにはwriteメソッドなどを使うが、書き込みについては回を改めて紹介する。

 今回最初に、ファイルの全ての文字列を読み込む方法を見た後、次回は1文字ずつ読み込む方法、次々回は1行ずつ読み込む方法を見ていく。では、最も簡単な例から始めよう。

全ての文字列を読み込む ― readメソッド

 とりあえず、テキストファイルの内容(=テキストデータ)を全て読み込みたいのであれば、openメソッドでファイルを開いたうえで、readメソッドを使えばよい。読み込みが終わったらcloseメソッドでファイルを閉じよう。

 以下の例では、data001.txtファイルを開き、内容を全て読み込んで、そのまま表示する。どのようなデータが読み込まれたかがよく分かるように、読み込んだデータをpメソッドを使って表示することにしよう。

sample001.rb
f = File.open("data001.txt")
s = f.read  # 全て読み込む
f.close
p s
リスト1.1 全ての文字列を読み込んで表示するプログラム

Fileクラスのopenメソッドの第1引数にファイルのパス名を指定すれば、そのファイルが開ける。openメソッドはFileオブジェクトへの参照を返すので、それを使って入出力のメソッドを呼び出せばよい。引数を指定せずにreadメソッドを呼び出すと、ファイルの内容がテキストデータとして全て読み込まれる。
なお、data001.txtファイルには任意の文字列を書き込んで、システム既定のエンコード(macOSならUTF-8、WindowsならShift_JISなど)で保存してほしい。エンコードによっては読み込んだテキストデータが文字化けする場合がある。

 実行結果は以下の通りである。macOSのテキストファイルでは改行文字をLFとするのが一般的であり、Windowsでは改行文字をCRLFとするのが一般的だが、LF\nと表示され、CRLF\r\n\n実行環境や後述するモード指定などによって異なる)と表示される。

ターミナル
$ more data001.txt 
青龍
朱雀
白虎
玄武
$ ruby sample001.rb 
"青龍\n朱雀\n白虎\n玄武"
実行例1.1 ファイルから全ての文字列を読み込んで表示する

最初に、moreコマンドを使ってdata001.txtファイルの内容を確認しておく。この例では、ファイルの末尾には改行文字は入力されていない。プログラムを実行すると、ファイルから全ての文字が読み込まれ、表示される。改行文字は\nまたは\r\nと表示される。

 readメソッドはテキストデータの読み込みだけでなく、バイナリデータの読み込みにも使える。その場合は、readメソッドの引数にバイト数を指定すればよい。

ブロックを使ってファイルを処理する

 Fileクラスのopenメソッドには、ブロックが指定できる。このとき、作成されたFileオブジェクトの参照がブロックの引数として渡される。したがって、リスト1.1は以下のように書き換えられる。なお、開かれたファイルはブロックの実行が終了すると自動的に閉じられる(リスト1.1のようにcloseメソッドを呼び出す必要がない)。

sample002.rb
s = ""  # これがないと、ブロック内の変数sが未定義となる
File.open("data001.txt"){|f|
  s = f.read
}
p s
リスト1.2 ブロックを使ってファイルを取り扱う

ブロック付きのopenメソッドでは、作成されたFileオブジェクトの参照がブロックの引数に渡される。ここでは引数fに渡される。以降の処理はリスト1.1と同様である。

 ここからは、ブロック付きのopenメソッドを使って説明を進めることとする。実行結果は、実行例1.1と同じなので省略する。

アクセスモードと文字コードを指定する

 テキストファイルの文字コードは作成時の環境や設定によって異なる。例えば、macOSではターミナルやテキストエディットの文字コードとしてUTF-8が標準的に使われており、Windowsのコマンドプロンプトやメモ帳ではShift_JISが標準的に使われている。当然のことながら、異なる文字コードのテキストファイルを読み込んでそのまま表示すると文字化けを起こしてしまう。

 以下の例は、Windowsで作成されたdata001_sjis.txtファイルを読み込むようにリスト1.1のプログラムを書き換え、macOSのターミナルで実行してみた様子である。data001_sjis.txtファイルの文字コードはShift_JISで、改行文字はCRLFとする。

ターミナル
$ ruby sample001.rb 
"\x90\u0097\xB4\r\n\x8E鐝\r\n\x94\x92\x8C\xD5\r\n\x8C\xBA\x95\x90"
$
実行例1.2 Shift_JIS文字コードでCRLF改行のファイルをmacOSで読み込んで表示する

入力ファイルの文字コードとしてUTF-8が想定されていたが、Shift_JISのファイルを読み込んだので正しく表示されなかった。改行文字も\r\nと表示されていることが分かる。

 このような場合には、openメソッドの第2引数にエンコーディングの種類を指定してファイルを開くとよい(リスト1.3)。

sample003.rb
s = ""
f = File.open("data001_sjis.txt", mode = "rt:sjis:utf-8"){|f|
  s = f.read  # 全て読み込む
}
p s
リスト1.3 openメソッドの第2引数にアクセスモードと文字コードを指定する

 このとき、openメソッドの第2引数にはアクセスモードを必ず指定しておく必要がある。具体的には、

  "アクセスモード:外部エンコーディング:内部エンコーディング"

の形式で書けばよい。

 アクセスモードとして指定したrtは、r(読み出し専用)、t(改行文字のCRLFCRLFは全てLFとして読み込む)を意味するので、テキスト読み込みモードでファイルを開くことになる。ちなみにアクセスモードとしてrだけを指定すると、プラットフォームによってrt(テキストモード)と見なされるか、rb(バイナリモード:改行コードはそのまま読み出す)と見なされるかが決まる。例えば、macOSではrbと見なされ、Windowsではrtと見なされる。この例であれば、アクセスモードにrまたはrbを指定すると、改行文字は\r\nと表示される。

 文字コードに関しては、外部エンコーディング(=読み込み対象である外部ファイルの文字コード)としてsjisを指定しているが、それだけだとShift_JISとして読み込まれるだけなので、ターミナルには正しく表示されない。そこで、内部エンコーディング(=プログラム内部で使う文字コード)としてutf-8を指定し、utf-8に変換した文字列を表示している。これで、macOSのターミナルでもShift_JISのファイル内容が正しく表示されるようになる。もちろん、Windows上で実行するなら、アクセスモードを指定しなくても実行例1.1と同じ結果になるが、明示的に指定するなら、"r:sjis"または"rt:sjis"を指定すればよい。なお、"rb:sjis"を指定すると、改行文字が\r\nと表示される。

 このプログラムも実行結果は実行例1.1で見た結果と同じなので省略する。

BOM(バイトオーダーマーク)を削除する

 UTF-8やUTF-16のファイルには、ファイルの先頭にBOMバイトオーダーマーク)と呼ばれるコードが付けられていることがある。これを削除するには、

  BOM|エンコーディング

と指定する。data001.txtファイルと内容は同じだが、先頭にBOMが付けられているdata001_bom.txtファイルから、BOMを削除する例を見てみよう。

sample004.rb
s = ""
f = File.open("data001_bom.txt", mode = "rt:BOM|utf-8"){|f|
  s = f.read  # 全て読み込む
}
STDOUT.write s  # 標準出力にそのまま出力
リスト1.4 BOMを削除するためのプログラム

入力ファイルの文字コードはUTF-8とする。BOMが付いていることを示すにはBOMとエンコーディングの|(OR)を取ればよい。

 実行結果を確認するためには、ファイルを16進ダンプする必要がある。macOSではhexdumpコマンドやodコマンドを使えばよい(実行例1.4a)。WindowsではPowerShellのFormat-Hexコマンドレットが使えるが、PowerShellではパイプラインへの出力は変数$OutputEncodingに設定されている文字コード(通常はus-ascii)にエンコーディングされ、標準出力への出力はUnicode(UTF-16、リトルエンディアン・バイトオーダーマーク付き)に変換されてしまうので一筋縄ではいかない。そこで、Set-Contentコマンドレットを使っていったんファイルに出力し、Format-Hexコマンドレットで16進ダンプを行うこととする(実行例1.4b)。

ターミナル
$ hexdump data001_bom.txt
0000000 ef bb bf e9 9d 92 e9 be 8d 0a e6 9c b1 e9 9b 80
0000010 0a e7 99 bd e8 99 8e 0a e7 8e 84 e6 ad a6     
000001e
$ ruby sample003.rb | hexdump
0000000 e9 9d 92 e9 be 8d 0a e6 9c b1 e9 9b 80 0a e7 99
0000010 bd e8 99 8e 0a e7 8e 84 e6 ad a6              
000001b
実行例1.4a BOMを削除する(macOS)

UTF-8、BOM付きのファイルをhexdumpコマンドで16進ダンプすると、先頭の3バイトにBOMのef bb bfが入っていることが分かる。プログラムを実行し、結果をパイプラインでhexdumpコマンドに渡す。先頭の3バイトが削除された。

ターミナル
> Format-Hex data001_bom.txt|more

           パス: C:\Users\hiros-h\Documents\RubyTips\data001_bom.txt

           00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000000   EF BB BF E9 9D 92 E9 BE 8D 0D 0A E6 9C B1 E9 9B  i≫?e??e??..a?±e?
00000010   80 0D 0A E7 99 BD E8 99 8E 0D 0A E7 8E 84 E6 AD  ..c??e??..c??a-
00000020   A6                                               |

> ruby sample003.rb|Set-Content -Path data001_n.txt
> Format-Hex data001_n.txt|more

           パス: C:\Users\hiros-h\Documents\RubyTips\data001_n.txt

           00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000000   E9 9D 92 E9 BE 8D 0D 0A E6 9C B1 E9 9B 80 0D 0A  e??e??..a?±e?..
00000010   E7 99 BD E8 99 8E 0D 0A E7 8E 84 E6 AD A6        c??e??..c??a-|
実行例1.4b BOMを削除する(Windows PowerShell)

あらかじめ、UTF-8、BOM付きのファイルをFormat-Hexコマンドレットで16進ダンプして、BOM(EF BB BF)が入っていることを確認しておく。パイプラインや標準出力に渡すと、データがus-asciiやUnicodeにエンコーディングされてしまうので、いったんプログラムの実行結果を作業用のdata001_n.txtファイルに出力しておき、その後、Format-Hexコマンドレットで16進ダンプする。先頭の3バイトが削除されたことが分かる。

まとめ

 ファイルを開くにはFileクラスのopenメソッドを使う。アクセスモードやエンコーディングはmode引数で指定できる。readメソッドは、ファイルの全てのテキストデータや指定したバイト数のバイナリデータを読み込むのに使う。

処理対象:文字列の読み込み(入力) カテゴリ:ファイル入出力
API:IOクラス|Fileクラス カテゴリ:組み込みライブラリ
API:openメソッド|closeメソッド カテゴリ:Fileクラス
API:readメソッド カテゴリ:IOクラス

※以下では、本稿の前後を合わせて5回分(第17回~第21回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

17. 数値/文字列/配列/範囲式/正規表現の比較を行うには?

Rubyプログラミングでは「等しいかどうか」を調べるための比較はどう行うのか? 比較を行える演算子やメソッドを使って、さまざまな比較を試してみる。

18. 演算子を再定義するには?

Rubyではクラスの二項演算子や単項プラス/マイナス演算子を定義(もしくは再定義)できる。その方法を基礎から説明し、実用的な使い方の例を示す。

19. 【現在、表示中】≫ ファイルから文字列を読み込む(入力する)には?(基本編)

テキストファイルから文字列を読み込むための基礎を解説。ファイル操作をブロックで記述する方法や、ファイルを開く際に「テキスト読み出し専用モード」でアクセスしたり文字コードを指定したりする方法、BOM付きファイルを処理する方法を説明する。

20. ファイルから1文字ずつ読み込む(入力する)には?

Rubyでテキストファイルから文字列を読み込むための方法として、ファイルから1文字単位で文字を取得する方法と、ファイル内の全テキスト内容を先頭から1文字ずつループ処理する方法を説明する。

21. ファイルから1行/段落ごと読み込む(入力する)には?

Rubyでテキストファイルから文字列を読み込むための方法として、ファイル内の全テキスト内容を先頭から1行単位ずつもしくは1段落ずつループ処理する方法と、ファイルから読み込んだ全ての行を配列として返す方法を説明する。

サイトからのお知らせ

Twitterでつぶやこう!