「標準入出力」の資料でも述べた通り,実用的なプログラムでは,しばしばファイルを介して大量のデータを扱います. ここでは,ファイルからデータを直接入力する,あるいはファイルへデータを直接に出力する方法について解説します.
もくじ
- 例題
- プログラム
- ファイル入出力の概要
- ファイルのオープン
- ファイルのクローズ
- ファイルの読み書きのためのメソッド
- ブロックつきメソッドによるファイルの入出力
- open/closeによるファイルの入出力の例
- 参考:ARGFによるファイルの読み込み
例題
プログラム
例題のプログラムをRubyで記述すると以下のようになります.
[行番号つきプログラムを別のウィンドウで開く] [行番号なしのプログラム]
このプログラムは次のように実行します.
$ ruby lineno.rb input.txt output.txt
input.txtの各行の先頭に行番号を添えて,output.txtに保存します. このとき行番号の桁数を自動的に調整します.
ファイル入出力の概要
ファイルを介した入出力を行う,つまりファイルからデータを読み出すあるいはファイルにデータを書き出すには,予めファイルと対応づけられた入出力ストリームを準備する必要があります. また入出力が終わってストリームが不要になったときには,後片付けをする必要があります.
ストリームを準備することをオープン(open)する,ストリームを片付けることをクローズ(close)するといいます. たとえばファイルからデータを読み込むには次のようにします.
- まずファイルをオープンします.
- つづいてオープンされたストリームから実際にデータの読み込みを行います.
- 読み込みが完了したらストリームをクローズします.
ファイルにデータを書き込む場合の手順もほぼ同様です.
- まずファイルをオープンします.
- つづいてオープンされたストリームに実際にデータの書き込みを行います.
- 書き込みが完了したらストリームをクローズします.
ファイルのオープン
ファイルをオープンしてストリームを得るにはopenメソッドを使います. openでは,オープンするファイルのパスとモード(mode)を指定します. モードとはファイルの扱い方を示すデータです. ファイルを読み込むために使う場合,ファイルに書き込むために使う場合など,目的に応じてモードを適宜指定する必要があります. モードは文字列で与えます. なおモードを省略すると読み込み専用でオープンすることになります("r"を指定したのと同じ).
open(ファイル名,モード)
| モード | 意味 | 
|---|---|
| "r" | 読み込み専用モード.指定したファイルがなければエラー | 
| "w" | 書き込み専用モード.指定したファイルがなければ新たに作成する.すでに存在した場合はファイルをいったん空にする. | 
| "a" | 追加書き込みモード | 
| "r+" | 読み書きモード.指定したファイルがなければエラー | 
| "w+" | 読み書きモード.指定したファイルがなければ新たに作成する.すでに存在した場合はファイルをいったん空にする. | 
| "a+" | 読み書きモード(読み込みは先頭から書き込みは末尾に追加) | 
| "b" | バイナリモード.他のモードと組み合わせて指定する.テキストファイルとバイナリファイルの扱いに区別があるシステムで利用する.Unix(Linux)ではテキストファイルとバイナリファイルの扱いに区別がないため,このモードを指定する必要はない(指定しても無害) | 
オープンの返り値はストリームに対応するオブジェクトです.
  f = open("foo.txt","r")
ファイルのクローズ
ファイルに対する入出力が終わったら必ずクローズします. クローズすることで処理が終わったことがシステムに伝えられます.
  f = open("foo.txt","r")
  :
  f.close # ファイルをクローズする  
ファイルの読み書きのためのメソッド
ストリームに対応するオブジェクトには,たとえばgetsやprintといったメソッドが定義されています. これらはこれまでに関数的メソッド使っていたgets,printと全く同じように使えます. レシーバを指定しなければ,標準入出力を使うことになり,レシーバとしてストリームに対応するオブジェクトを指定すれば,そのストリームに対する入出力を行うことになるわけです.
Rubyにはファイルを読み書きするためのメソッドが他にもいろいろ用意されています. ここでいくつか例を挙げます. 詳しくはRubyのIOクラスのマニュアルを参照してください.
| 読み込みメソッド | |
|---|---|
| each,each_line | 一行ずつ読むイテレータ | 
| each_byte | 1バイトずつ読み込むイテレータ | 
| gets | 1行読み込む | 
| read | 指定したバイト数だけ読み込む.バイト数を指定しない場合はすべて読み込む | 
| 書き込みメソッド | |
| << | 引数に指定したオブジェクトをファイルに書き出す. | 
| 引数として指定したオブジェクトを順に書き出す. | |
| puts | 引数として指定したオブジェクトに改行をつけてから順に書き出す. | 
| write | 引数に指定したオブジェクトをファイルに書き出す. | 
ブロックつきメソッドによるファイルの入出力
openはブロックつきメソッドとして利用することもできます. この場合ブロック内でファイルの処理を行うことになります. ストリームに対応するオブジェクトはブロックパラメタとしてブロックに渡されます. またブロックの実行終了時にファイルが自動的にクローズされます. つまりブロックつきメソッドで処理した場合,closeを明示的に実行する必要がありません. ここで詳細は述べませんが,例外処理を行うことを考えた場合, ブロックつきメソッドで処理した方がプログラムが簡単になります.
例題のプログラムでもブロックつきメソッドで処理を行っています.
21  File.open(infile,"r") do |fin|
22    while line = fin.gets
23      in_lines.push(line)
24    end
25  end
32  File.open(outfile,"w") do |fout|
33    in_lines.each_with_index do |line,j|
34      fout.puts fmt % [j+1,line]
35    end
36  end
open/closeによるファイルの入出力の例
open/closeを明示的に使ったプログラムの例を挙げます. このプログラムは例題のプログラムとほぼ同様のものです. ただし出力ファイルが指定されないか「"-"」が指定された場合には標準出力に結果を書き出すようになっています.
[行番号つきプログラムを別のウィンドウで開く] [行番号なしのプログラム]
このプログラムは,たとえば次のように実行します.
$ ruby lineno2.rb input.txt outfile.txt # outfile.txtに書き出す. $ ruby lineno2.rb input.txt - # 標準出力に書き出す. $ ruby lineno2.rb input.txt # 標準出力に書き出す.
参考:3項演算子
上のプログラムの次の行では「3項演算子」を利用しています.
32  fout = (outfile == "-" or (not outfile)) ? STDOUT : File.open(outfile,"w")
3項演算子によってif式を簡易的に表現することができます. 3項演算子は次のように定義されます.
条件式 ? 式1 : 式2
条件式が真なら式1,偽なら式2の値を返します.
これをif式にすると次のようになります(注: Rubyでは「if」は式です).
if 条件式; 式1; else 式2; end
参考:ARGFによるファイルの読み込み
RubyにはARGFというオブジェクトが定義されています. さてプログラム実行時にコマンドライン引数としてファイルをいくつか指定したとします. それらのファイルの内容をすべて一列につなげた仮想的なファイルを考えます. ARGFはこのような仮想ファイルに対応するストリームとして働くオブジェクトです. なお引数は実際にファイルであろうとなかろうとファイル名とみなされます(引数がファイルであることを確認するわけではありません). また引数がない場合は,ARGFは標準入力に対応することになっています.
ARGFを利用することで,ファイルが与えられればファイルからデータを読む,そうでなければ標準入力からデータを読むというプログラムを簡単に作ることができます(Unixではこのようなスタイルのプログラムがよく使われます).
ARGFは関数的メソッドであるgetsの隠されたレシーバです. つまり「gets」を「ARGF.gets」と書くこともできます. そこで,例題のプログラム(に近いもの)を次のように書くこともできます. このプログラムは複数のファイルを処理することができます. ただ単純には出力ファイルを決められないため,結果は標準出力に送っています.
[行番号つきプログラムを別のウィンドウで開く] [行番号なしのプログラム]
このプログラムは,たとえば次のように実行します.
$ ruby lineno3.rb input0.txt input1.txt
 日置尋久(HIOKI Hirohisa)
Last modified: Sun Mar 22 22:44:51 JST 2020
日置尋久(HIOKI Hirohisa)
Last modified: Sun Mar 22 22:44:51 JST 2020