Ruby TIPS
ファイルから文字列を読み込む(入力する)には?(基本編)
テキストファイルから文字列を読み込むための基礎を解説。ファイル操作をブロックで記述する方法や、ファイルを開く際に「テキスト読み出し専用モード」でアクセスしたり文字コードを指定したりする方法、BOM付きファイルを処理する方法を説明する。
Rubyでテキストファイルを開くときには、アクセスモード(読み込み、書き込みなど)やエンコーディング(文字コード)の指定が必要である。また、ファイルから文字列の読み込みや書き込み(=入出力)の際には、全体を読み込むのか、行や文字ごとに読み込むのか、と考慮すべき事柄がたくさんある。全てを一度に学ぶのは大変なので、今回は、読み込み(入力)に絞り、基本的な方法を示す。さらに後日公開予定の「ファイルから1文字ずつ読み込む(入力する)には?」(次回)と「ファイルから1行ごと/段落ごと読み込む(入力する)には?」(次々回)で、読み込み機能に関連するさまざまなメソッドを、順を追って少しずつ見ていく。
ファイル入出力の基本 − 入力編
ファイルを利用するには、一般的にはFile
クラスのopen
メソッドを使ってファイルを開き、read
メソッドやreadline
メソッドなどを使ってデータを読み込む。これらの入出力メソッドはIO
クラスのメソッドなので、ドキュメントを参照する際にはIO
クラスも併せて見るようにしよう(File
クラスはIO
クラスを継承しているので、それらのメソッドが利用できる)。なお、書き込みにはwrite
メソッドなどを使うが、書き込みについては回を改めて紹介する。
今回最初に、ファイルの全ての文字列を読み込む方法を見た後、次回は1文字ずつ読み込む方法、次々回は1行ずつ読み込む方法を見ていく。では、最も簡単な例から始めよう。
全ての文字列を読み込む ― readメソッド
とりあえず、テキストファイルの内容(=テキストデータ)を全て読み込みたいのであれば、open
メソッドでファイルを開いたうえで、read
メソッドを使えばよい。読み込みが終わったらclose
メソッドでファイルを閉じよう。
以下の例では、data001.txt
ファイルを開き、内容を全て読み込んで、そのまま表示する。どのようなデータが読み込まれたかがよく分かるように、読み込んだデータをp
メソッドを使って表示することにしよう。
f = File.open("data001.txt")
s = f.read # 全て読み込む
f.close
p s
|
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玄武"
|
最初に、more
コマンドを使ってdata001.txt
ファイルの内容を確認しておく。この例では、ファイルの末尾には改行文字は入力されていない。プログラムを実行すると、ファイルから全ての文字が読み込まれ、表示される。改行文字は\n
または\r\n
と表示される。
read
メソッドはテキストデータの読み込みだけでなく、バイナリデータの読み込みにも使える。その場合は、read
メソッドの引数にバイト数を指定すればよい。
ブロックを使ってファイルを処理する
File
クラスのopen
メソッドには、ブロックが指定できる。このとき、作成されたFile
オブジェクトの参照がブロックの引数として渡される。したがって、リスト1.1は以下のように書き換えられる。なお、開かれたファイルはブロックの実行が終了すると自動的に閉じられる(リスト1.1のようにclose
メソッドを呼び出す必要がない)。
s = "" # これがないと、ブロック内の変数sが未定義となる
File.open("data001.txt"){|f|
s = f.read
}
p s
|
ブロック付きの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"
$
|
入力ファイルの文字コードとしてUTF-8が想定されていたが、Shift_JISのファイルを読み込んだので正しく表示されなかった。改行文字も\r\n
と表示されていることが分かる。
このような場合には、open
メソッドの第2引数にエンコーディングの種類を指定してファイルを開くとよい(リスト1.3)。
s = ""
f = File.open("data001_sjis.txt", mode = "rt:sjis:utf-8"){|f|
s = f.read # 全て読み込む
}
p s
|
このとき、open
メソッドの第2引数にはアクセスモードを必ず指定しておく必要がある。具体的には、
"アクセスモード:外部エンコーディング:内部エンコーディング"
の形式で書けばよい。
アクセスモードとして指定したrt
は、r
(読み出し専用)、t
(改行文字のCR
/LF
/CRLF
は全て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を削除する例を見てみよう。
s = ""
f = File.open("data001_bom.txt", mode = "rt:BOM|utf-8"){|f|
s = f.read # 全て読み込む
}
STDOUT.write s # 標準出力にそのまま出力
|
入力ファイルの文字コードは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
|
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-|
|
あらかじめ、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プログラミングでは「等しいかどうか」を調べるための比較はどう行うのか? 比較を行える演算子やメソッドを使って、さまざまな比較を試してみる。
19. 【現在、表示中】≫ ファイルから文字列を読み込む(入力する)には?(基本編)
テキストファイルから文字列を読み込むための基礎を解説。ファイル操作をブロックで記述する方法や、ファイルを開く際に「テキスト読み出し専用モード」でアクセスしたり文字コードを指定したりする方法、BOM付きファイルを処理する方法を説明する。
20. ファイルから1文字ずつ読み込む(入力する)には?
Rubyでテキストファイルから文字列を読み込むための方法として、ファイルから1文字単位で文字を取得する方法と、ファイル内の全テキスト内容を先頭から1文字ずつループ処理する方法を説明する。
21. ファイルから1行/段落ごと読み込む(入力する)には?
Rubyでテキストファイルから文字列を読み込むための方法として、ファイル内の全テキスト内容を先頭から1行単位ずつもしくは1段落ずつループ処理する方法と、ファイルから読み込んだ全ての行を配列として返す方法を説明する。