(060) ファイル入出力

Ippei Kishida

Last-modified:2018/03/29 22:55:15.

大量のデータを処理するプログラムでは、 多くの場合 データはファイルとしてプログラム本体とは別に保存されている。 こうすることでプログラムを変更することなくデータファイルを差し替えるだけで 簡単に出力を得ることができる。 もしプログラムを変更した場合、基本的には修正に対する検証作業(テストとデバッグ) が必要になってくるが、そんな面倒なこと誰もやりたくないだろう。 また、ロールプレイングゲームやオフィスアプリケーションのように、 前回作業した状態から再開したい場合には必須の機能と言えよう。 本章では「プログラム本体からのデータの書き出し」および 「保存されたデータの読み込み」を行う。

1 画面への出力

これまでに画面への出力として、printp について説明した。 これらの違いをキチンと押えられているだろうか? p はデバッグ用に値を確認するためのもので勝手に改行が足されたりするのだが、 print はプログラムからの正式な出力を表示するためのものだった。 この print に一工夫してやることでファイルへの書き出しができるようになるので、 それを見ていこう。

2 ファイルへの書き出し

ファイルへの書き出しを行うには、 出力するべきファイル名でファイルハンドルをオープンしてやる必要がある。

# fileOutput.rb
io = File.open("output.txt", "w") #output.txt は上書きされるので注意
io.print "hello, world!\n"
io.print "good-bye!\n"
io.close

何も言わずに、あっけなくプログラムが終了するだろうが、 ソースファイルと同じフォルダを見ると新しく output.txt というファイルが 生成されている筈なので、開いて内容を確認すること。 1

なおファイルの書き出しは、 既に同名ファイルが存在すればそれを破壊することになるので、 十分に注意を払うこと。

コードの最後で io.close とファイルハンドルをクローズしている。 クローズし忘れてもプログラム終了時に自動的に閉じてくれるのだが、 オープンしたものはクローズしておいた方が バグが混入する危険性が減って、お行儀の良いプログラムとなる。

3 フォーマットを揃えて書き出す(printf)

以下のコードを実行してみよう。

#printf.rb
5.times do |i|
  number = 10 ** (rand*10)
  print number, "\n"
end

実行すると0〜\(10^{10}\) の間の数が10個羅列される。 ここで使っている rand は 0.0〜1.0 の間の数をランダムで出すもので、 rand*10 によって 0.0〜10.0 の間の数をランダムに取り出せることになる。 ** は累乗の演算子なので、\(10^0\)\(10^{10}\) の間の数を取り出すことになる。 さて、このプログラムからの出力の一例を下に示すが、 桁の位置が揃っていないのであまりに見難い。

44418421.8590911
110.368131065036
4198640.91704631
7207.51252355368
1.46014200258896

このような数でも頑張って読めば読めるし、 コンピュータに読ませるデータとしては問題ないのだが、 しかしやっぱり人間に優しくない。 このようなときは printf を使ってprint の行を以下のように 書換えると綺麗に表示できる。

#printf.rb
5.times do |i|
  number = 10 ** (rand*10)
  #print number, "\n"
  printf("%30.15f\n", number)
end

この実行結果は、以下のような形式になる。

    4378293117.907456398010254
    4539719662.505616188049316
            27.609244484055377
          7877.950991543454620
           235.775798519118382

3.1 解説

printf("%30.15f\n", number) を読み解こう。 printf というのは関数で、() で囲まれた引数を取る。 () の中にはカンマ区切りで2つの要素があり、 その1つめは "" で囲まれた文字列「%30.15f\n」。 この「%30.15f」はそこに変数を挿入するという指示を示し、 この部分の最後の f が浮動小数点数を入れることを意味する。 30.15 は 全体で30桁の領域を出力用に確保し、小数点以下を15桁にする (すなわち小数点に1桁、整数部を14桁にする)ことを意味する。

このように確保された領域に、第2引数の number が挿入される。

たとえば「4桁の領域を確保してそこに整数の変数 i を入れる」としたければ 「printf("%4d\n", i)」。 ここで「d」 は入る変数が整数(digit) であるという指示。

3.2 次のファイル入力のための準備 {#sec20100405a}

[課題] ここで説明した方法を駆使して、5個の数をファイルに書き出せ。 ファイル名は number.txt とすること。

4 ファイルからの読み込み

ファイルの読み書きをする為にも、OS(Linux や Windows) に頼んでファイルをオープンする必要がある。 以下のサンプルプログラムを参考のこと。

# fileInput.rb
io = File.open("number.txt", "r") 
array = io.readlines #ファイルの全内容を1行ごとに配列に格納
io.close
p array

5 キーボードからの入力

ユーザからの入力を受け付けるには STDIN.gets を使う。

#stdin.rb を作る。
STDIN.gets

実行してみよう。 すると、

% ruby stdin.rb
_

のように次の行でカーソルが待機する。 これはプログラムがキーボード入力を待っている状態なので、 ここで Enter を打ってやるとプログラムが進み、終了する。

普通のユーザはこの入力待ちの状態に面くらう。 プログラムが何か他の処理をしているのか、何らかの異常で停止しているのか迷うだろう。 ユーザの入力を促す表示をしてやるべきだ。

print "Enter を押してください\n"
STDIN.gets

さて、受け取った文字を他で使用するために、変数に格納してみよう。

print "文字列を入力し、Enter を押してください\n"
str = STDIN.gets
p str

これを実行すると、

C:\Documents and Settings\ippei/>
文字列を入力し、Enter を押してください
test
"test\n"

3行目は私が打ち込んだ文字列で、 それを p で表示したものが4行目である。 p で表示すると改行文字が「\n」で表されるのだが、 文字列に改行文字が入っていることが確認できる。 これはユーザ入力が Enter でなされるためである。 この行末の改行文字が若干困ったことになることがある。 例えば、

print "「test」と入力し、Enter を押してください\n"
str = STDIN.gets
if ('test' == str)
    print "OK\n"
else
    print "NG\n"
end

のコードを実行してみよう。 正しく「test」と打っても NG と表示される筈だ。 これは「test」と「test\n」を比較して、 等しくないと判断されるためである。

これを避けるため、入力文字列には chomp! を作用させる。

print "「test」と入力し、Enter を押してください\n"
str = STDIN.gets
str.chomp!
if ('test' == str)
    print "OK\n"
else
    print "NG\n"
end

これで正しく動作する。 ここでは、

str = STDIN.gets
str.chomp!

のように、まず STDIN.gets の結果を str に入れ、str を chomp! したが、

str = STDIN.gets.chomp

のように、STDIN.gets の結果を chomp! したものを str に入れても同じ結果が得られる。

6 課題

  1. Sec. 3.2 で提示した number.txt を生成するプログラムコードを示せ。

  1. 逆に言えば、ファイルハンドルに連結していない print 文などは、 ファイルハンドルのかわりに 標準出力(コンソール画面、プログラムを実行する黒いウィンドウ)に連結していると言える。