roy > naoya > 基礎プログラミングI·情報検索 > (10)ファイルの入出力
前回の課題(kadai6.rb)はgetsメソッドを使って外部ファイルから1行ずつ読み込み特定の処理を行うものであった。読み込むファイルの指定は、ktermでプログラムを実行する際に
pan{c10xxxx}% ruby kadai6.rb work.txt
というように、実行するプログラム名の後ろに記述した。これにより指定した任意のファイルを自由に読み込むことが可能となった。
しかし、こうした指定方法にはデメリットもある。
これらの問題に対処するために準備されているのがopenメソッドである。openメソッドを使うとプログラム内でファイルオープンやクローズを行うことができる。
openメソッドの基本構造
open("ファイル","モード")do |変数| 開いたファイルを使って行う処理 end
ファイル:ファイル名を指定する。ファイルとプログラムは原則として同一のディレクトリに置く。ディレクトリが異なる場合はファイル名のみでなくパスを指定する(例えば../ruby/hoge.txt)
モード:指定したファイルを開いて読み込むのか、指定したファイルを作成して書き込むのか、既存のファイルを開いて追加書き込みするのか等、モードを指定する。以下の6種類から選ぶ。
変数:printfやprintメソッドの表示先、getsメソッドの読み込み元を指定する際に使用する。任意の名前でよい。
前回の授業で取り上げたanalysis.rbを改良し、openメソッ ドを使ってファイルからのデータの読み込みができるように書き換えてみよう(open.rb)。
#!/usr/koeki/bin/ruby #初期設定 number = [] #学籍番号を代入する配列 score = [] #得点を代入する配列 sum = 0 #合計を代入する配列 n = 0 #配列に使用するインデックス i = 0 #配列に使用するインデックス #学籍番号と得点の読み込みと合計の計算 open("data.txt","r") do |test| while line = test.gets if /(\S+)\s+(\d+)/ =~ line number[n] = $1 score[n] = $2.to_f sum += $2.to_f n += 1 end #ifに対するend end #whileに対するend end #openに対するend #個々人の得点と平均点との差 average = sum / n STDERR.print"- 学籍番号 ------ 得点 ---- 差 - \n" STDERR.print"\n" while i < n STDERR.printf(" %-10s \t %3d \t %6.1f\n", number[i],score[i],score[i]-average) i += 1 end
変更点は黄色で示している。open ("data.txt","r") do |test|としているので、開くファイル名はdata.txtである。モードはrなので読み込み専用である。また変数名はtestとしている。
open-end内でファイルから1行ずつ読み込み、正規表現にマッチさせて()内にマッチする学籍番号と得点をそれぞれ$1、$2で取り出し各変数に代入している。
プログラムを実行すると、analysis.rbと同じ結果を得る。ただし、analysis.rbはktermで実行する際に後ろに読み込むファイル名の指定が必要であったのに対し、open.rbはopenメソッドを使ってプログラム内で開くファイルを指定しているので、ktermでの実行時にはファイル名の指定はしていない。
pan{c10xxxx}% ruby open.rb[Return] - 学籍番号 ------ 得点 ---- 差 - c110001 45 -4.9 c110002 52 2.1 c110003 38 -11.9 c110004 60 10.1 c110005 44 -5.9 c110006 67 17.1 c110007 50 0.1 c110008 57 7.1 c110009 41 -8.9 c110010 45 -4.9
ファイルからの読み込みを行うgetsメソッドやディスプレイにkekkaを表示するprintfメソッドの前にtestやSTDERRなどがついている。getsメソッドはキーボードとファイルからデータを読み込むことができ、printメソッドやprintfメソッドはディスプレイとファイルに出力することができる。したがって、どこから読み込みどこに出力するのかを指定しなければ、適切に動作をすることができなくなる。
読み込み元や読込先が複数ある場合の対応
今度はopenメソッドを使ってファイルへの書き込みを行う。printメソッドやprintfメソッドの表示先をディスプレイではなくファイルにすることで、ファイルへの書き込みが行われる(open2.rb)。
#!/usr/koeki/bin/ruby number = [] score = [] i = 0 j = 0 open("data2.txt", "a") do |mark| while true STDERR.print"学籍番号を入力してください(終了はq)\n" number[i] = STDIN.gets.chomp! if number[i] == "q" then break end STDERR.print"得点を入力してください\n" score[i] = STDIN.gets.chomp!.to_i i += 1 end mark.print"学籍番号 点数\n" while j < i mark.printf("%-10s \t %3d\n", number[j],score[j]) j += 1 end STDERR.print"ファイルへの書き込み終了です。\n" end
このプログラムは実行すると学籍番号と得点をたずね、順番に入力していき、これらをファイル(data2.txt)に出力するものである。
pan{c10xxxx}% ruby open2.rb[Return] 学籍番号を入力してください(終了はq) c1080014[Return] 得点を入力してください 45[Return] 学籍番号を入力してください(終了はq) c1080026[Return] 得点を入力してください 68[Return] 学籍番号を入力してください(終了はq) q[Return] ファイルへの書き込み終了です。
結果をファイルに出力するので、プログラムを実行しただけではうまく結果が得られているのかどうかわからない。では、このプログラムを実行したことにより生成されたdata2.txtの中身を確認してみよう。
中身を確認する場合、emacsでdata2.txtを開いても良いが、単に中身を確認するだけであればkterm上でcatコマンドやlessコマンド(詳細はUNIX主要コマンドのページを確認)を使用してもよい。cat ファイル名でそのファイルの中身を見ることができる。
pan{c10xxxx}% cat data2.txt[Return] 学籍番号 点数 c1080014 45 c1080026 68
ファイルの中身を確認すると、open.rbで読み込みに使用したdata.txtと同一の構造をとっていることがわかる。つまり、このプログラムでopen.rbで使用するデータを作成することができる。
若干繰り返しになるが、openメソッドに関連する部位について確認しよう。
openメソッドを使って、data2.txtというファイルを開いている。モードはaなので追加書き込み専用となる。はじめてこのプログラムを実行した際にはdata2.txtというファイルは存在しないため新規作成される。2回目以降に実行した場合はdata2.txtは存在しているので、データの末尾に追加で書き込みが行われる。do |mark|のmarkはファイルに書き込みを行う際の変数として利用している(後述)。
複数のデータ読み込み元、複数のデータ出力先がある場合は、どこから読み込むのか、どこに出力するのかを指定する必要がある。キーボードからの入力を読み込むためにはSTDIN.gets、ファイルからの読み込みはmark.gets(このプログラムでは使用していない)を用いる。
ディスプレイに出力する場合にはSTDERR.print、STDERR.printf、ファイルに出力する場合にはmark.print、mark.printfを用いる。
通常のprintfにmark.がついている。このmarkは、openメソッドの行で指定した変数名である。変数名.メソッドとすると、そのメソッドが働く先はファイルとなる。ここではmark.printfとしてファイルに出力している。
ファイルに出力する内容は、number[j]とscore[j]であり、numberには学籍番号、scoreには得点が代入されている。while-end内でjの値を1ずつ増加しながら読み出しているため、結果的に全てのデータがファイルに書き込まれる。学籍番号は文字であるため、printfの書式制御文字は%sとしている。\tはキーボードのtabキーの効果を持ちスペースよりも広い空白を挿入する。
open.rbとopen2.rbを合体するとデータの入力と分析を1つのプログラムで行うことができるようになる。if文を設けて、入力モードと分析モードのどちらを使用するか選択できるようにする。
以下のプログラム(open_final.rb)はif文を使って入力モードと分析モードのどちらも利用できるようにしている。黄色で強調された部分が追加されているだけで、あとはopen.rbとopen2.rbをそのまま使用している。ただし、ファイル名だけはtest.txtで統一している。入力モードと分析モードで参照するファイルが異なると意味がないからである。
#!/usr/koeki/bin/ruby STDERR.print"入力モードは1、分析モードは2を入力\n" mode = STDIN.gets.chomp!.to_i if mode == 1 number = [] score = [] i = 0 j = 0 open("test.txt", "a") do |mark| while true STDERR.print"学籍番号を入力してください(終了はq)\n" number[i] = STDIN.gets.chomp! if number[i] == "q" then break end STDERR.print"得点を入力してください\n" score[i] = STDIN.gets.chomp!.to_i i += 1 end mark.print"学籍番号 点数\n" while j < i mark.printf("%-10s \t %3d\n", number[j],score[j]) j += 1 end STDERR.print"ファイルへの書き込み終了です。\n" end elsif mode == 2 number = [] score = [] sum = 0 n = 0 i = 0 open("test.txt","r") do |test| while line = test.gets if /(\S+)\s+(\d+)/ =~ line number[n] = $1 score[n] = $2.to_f sum += $2.to_f n += 1 end end end average = sum / n STDERR.print"- 学籍番号 ------ 得点 ---- 差 - \n" STDERR.print"\n" while i < n STDERR.printf(" %-10s \t %3d \t %6.1f\n", number[i],score[i],score[i]-average) i += 1 end else STDERR.print"そんなモードないです。\n" end
open2.rbを繰り返し実行した場合、data2.txtはどのように変化するだろうか。最低でも2回実行し、1回実行するごとにcatコマンドを使ってファイル内を確認する。その後、モードをaからwに変更し2回実行する。こちらも毎回catコマンドでdata2.txtの内部を確認する。この結果としてわかったことをまとめよ。
制限時間は10分。出席点は2点。提出要領は下記の通り。
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:Mewによるメールの送り方はMewコマンドを参照
以下の3種類の課題からどれか1つを選んで実施する(kadai8.rb)。採点は以下の通り行う。
問題2、問題3の実行結果のイメージを示す。
問題2
pan{c10xxxx}% ruby kdadai7.rb 家計簿入力モードは1、エンゲル係数計算は2 を入力:1 日付を入力:6月21日 曜日を入力:土 食費を入力:530 :
問題3
pan{c10xxxx}% ruby kdadai7.rb 家計簿入力モードは1、エンゲル係数計算は2 を入力:1 6月21日(土)の支出を入力します 食費を入力:530 :
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:ktermでのプログラムの実行結果をメールに貼り付けるには、コピーしたい箇所をマウスで選択し、emacs(Mew)上でマウスの真ん中ボタンをクリックする
Tips:Mewによるメールの送り方はMewコマンドを参照