roy > naoya > 基礎プログラミングI > (8)ファイルの入出力
(8) 06/04の授業内容:ファイルの入出力
[1] openメソッド
前回の課題(report5.rb)はgetsメソッドを使って外部ファイルから1行ずつ読み込み特定の処理を行うものであった。読み込むファイルの指定は、ktermでプログラムを実行する際に
sime{c11xxxx}% ./kadai5.rb result1.txt
というように、実行するプログラム名の後ろに記述した。これにより指定した任意のファイルを自由に読み込むことが可能となった。
しかし、こうした指定方法にはデメリットもある。
- ファイル名の入力間違いが生じる可能性がある
- ファイル名を指定し忘れる可能性がある
- 常に同じファイルを用いるのであれば、いちいち指定するのがわずらわしい
これらの問題に対処するために準備されているのがopenメソッドである。openメソッドを使うとプログラム内でファイルオープンやクローズを行うことができる。
openメソッドの基本構造
open("ファイル","モード:euc-jp")do |変数| 開いたファイルを使って行う処理 end
- ファイル:ファイル名を指定する。ファイルとプログラムは原則として同一のディレクトリに置く。ディレクトリが異なる場合はファイル名のみでなくパスを指定する(例えば../ruby/hoge.txt)。
- モード:指定したファイルを開いて読み込むのか、指定したファイルを作成して書き込むのか、既存のファイルを開いて追加書き込みするのか等、モードを指定する。以下の6種類から選ぶ。
- r:読み込み専用。
- r+:読み込み/書き込み用。
- w:書き込み専用。指定したファイルを新規作成する(既存のファイルを指定した場合は自動的に削除され、新規作成される)。
- w+:読み込み/書き込み用で、その他はwと同じ。
- a:追加書き込み専用。指定ファイルが存在しない場合は新規作成し、既存ファイルを指定した場合はデータの末尾に続けて書き込みを行う。
- a+:読み込み/追加書き込み専用で、その他はaと同じ。
- 変数:printfやprintメソッドの表示先、getsメソッドの読み込み元を指定する際に使用する。任意の名前でよい。
[2] openメソッドを使ったファイルからの読み込み
前回の授業で取り上げたreg_ex3.rbを改良し、openメソッドを使ってファイルからのデータの読み込みができるように書き換えてみよう(open_read.rb)。
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
#初期設定
number = [] #学籍番号を代入する配列
score = [] #得点を代入する配列
sum = 0 #合計を代入する配列
n = 0 #配列に使用するインデックス
i = 0 #配列に使用するインデックス
#学籍番号と得点の読み込みと合計の計算
open("data.txt","r:utf-8") do |test|
#:utf-8はdata.txtを文字コードutf-8で開くという意味
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"
while i < number.length
STDERR.printf(" %-10s \t %3d \t %6.1f\n",
number[i],score[i],score[i]-average)
i += 1
end
変更点は黄色で示している。open ("data.txt","r:utf-8") do |test|としているので、開くファイル名はdata.txtである。モードはrなので読み込み専用である。rの後ろに:utf-8とあるが、これは文字コードをutf-8形式で開くという意味である。本学のシステムでは文字コードとしてutf-8を使用しているので、決まり文句であると覚えよう。なお変数名はtestとしている。
open-end内でファイルから1行ずつ読み込み、正規表現にマッチさせて()内にマッチする学籍番号と得点をそれぞれ$1、$2で取り出し各変数に代入している。
プログラムを実行すると、reg_ex3.rbと同じ結果を得る。ただし、reg_ex3.rbはktermで実行する際に後ろに読み込むファイル名の指定が必要であったのに対し、open_read.rbはopenメソッドを使ってプログラム内で開くファイルを指定しているので、ktermでの実行時にはファイル名の指定はしていない。
sime{c11xxxx}% chmod +x open_read.rb[Return] sime{c11xxxx}% ./open_read.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メソッドやディスプレイにメッセージを表示するprintfメソッドの前にtestやSTDERRなどがついている。getsメソッドはキーボードとファイルからデータを読み込むことができ、printメソッドやprintfメソッドはディスプレイとファイルに出力することができる。したがって、どこから読み込みどこに出力するのかを指定しなければ、適切に動作をすることができなくなる。
読み込み元や読込先が複数ある場合の対応
- キーボードからの読み込みSTDIN.gets
- ファイルからの読み込み変数.gets(上記のプログラムではtest.gets)
- ディスプレイへの表示STDERR.print、STDERR.printf
- ファイルへの出力変数.print、変数.printf(上記のプログラムでは、test.print、test.printf)
[3] openメソッドを使ったファイルへの書き込み
今度はopenメソッドを使ってファイルへの書き込みを行う。printメソッドやprintfメソッドの表示先をディスプレイではなくファイルにすることで、ファイルへの書き込みが行われる(open_write.rb)。
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
number = []
score = []
i = 0
j = 0
open("data2.txt", "a:utf-8") 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 < number.length-1
mark.printf("%-10s \t %3d\n", number[j],score[j])
j += 1
end
STDERR.print"ファイルへの書き込み終了です。\n"
end
このプログラムは、学籍番号と得点の入力を促し、順番に入力された値を受け取ってファイル(data2.txt)に出力するものである。
sime{c11xxxx}% chmod +x open_write.rb[Return] sime{c11xxxx}% ./open_write.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 ファイル名でそのファイルの中身を見ることができる。
sime{c11xxxx}% cat data2.txt[Return] 学籍番号 点数 c1080014 45 c1080026 68
ファイルの中身を確認すると、open_read.rbで読み込みに使用したdata.txtと同一の構造をとっていることがわかる。つまり、このプログラムでopen_read.rbで使用するデータを作成することができる。
若干繰り返しになるが、openメソッドに関連する部位について確認しよう。
open ("data2.txt", "a:utf-8") do |mark|
openメソッドを使って、data2.txtというファイルを開いている。モードはaなので追加書き込み専用となる。はじめてこのプログラムを実行した際にはdata2.txtというファイルは存在しないため新規作成される。2回目以降に実行した場合はdata2.txtは存在しているので、データの末尾に追加で書き込みが行われる。do |mark|のmarkはファイルに書き込みを行う際の変数として利用している(後述)。
STDINとSTDERR、mark
複数のデータ読み込み元、複数のデータ出力先がある場合は、どこから読み込むのか、どこに出力するのかを指定する必要がある。キーボードからの入力を読み込むためにはSTDIN.gets、ファイルからの読み込みはmark.gets(このプログラムでは使用していない)を用いる。
ディスプレイに出力する場合にはSTDERR.print、STDERR.printf、ファイルに出力する場合にはmark.print、mark.printfを用いる。
mark.printf ("%-10s \t %3d\n", number[j],score[j])
通常のprintfにmark.がついている。このmarkは、openメソッドの行で指定した変数名である。変数名.メソッドとすると、そのメソッドが働く先はファイルとなる。ここではmark.printfとしてファイルに出力している。
ファイルに出力する内容は、number[j]とscore[j]であり、numberには学籍番号、scoreには得点が代入されている。while-end内でjの値を1ずつ増加しながら読み出しているため、結果的に全てのデータがファイルに書き込まれる。学籍番号は文字であるため、printfの書式制御文字は%sとしている。\tはキーボードのtabキーの効果を持ちスペースよりも広い空白を挿入する。
[4] 出席課題
open_write.rbを繰り返し実行した場合、data2.txtはどのように変化するだろうか。最低でも2回実行し、1回実行するごとにcatコマンドを使ってファイル内を確認する。その後、モードをaからwに変更し2回実行する。こちらも毎回catコマンドでdata2.txtの内部を確認する。この結果としてわかったことをまとめよ。
制限時間は10分。出席点は2点。提出要領は下記の通り。
- 提出先:課題提出用メールアドレス
- メールのSubject:ruby08
- 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降はcatコマンドで確認したdata2.txtを貼りつける。最低でも4回実行するので4回分(以上)貼りつける。その後わかったことを記載する。
Tips:emacsでの日本語入力のオンオフはCtrl+oです
Tips:Mewによるメールの送り方はMewコマンドを参照
[5] open_read.rbとopen_write.rbを合体しよう
open_read.rbとopen_write.rbを合体するとデータの入力と分析を1つのプログラムで行うことができるようになる。if文を設けて、入力モードと分析モードのどちらを使用するか選択できるようにする。
以下のプログラム(open_final.rb)はif文を使って入力モードと分析モードのどちらも利用できるようにしている。黄色で強調された部分が追加されているだけで、あとはopen_read.rbとopen_write.rbをそのまま使用している。ただし、ファイル名だけはtest.txtで統一している。入力モードと分析モードで参照するファイルが異なると意味がないからである。
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
STDERR.print"入力モードは1、分析モードは2を入力\n"
mode = STDIN.gets.chomp!.to_i
if mode == 1
number = []
score = []
i = 0
j = 0
open("test.txt", "a:utf-8") 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 < number.length
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:utf-8") 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 < number.length
STDERR.printf(" %-10s \t %3d \t %6.1f\n",
number[i],score[i],score[i]-average)
i += 1
end
else
STDERR.print"そんなモードないです。\n"
end
[6] レポート課題
次週からは、次週からは2進数や16進数などのプログラム内のデータ表現や、作成したプログラムの公開に話が移っていく。プログラム自体を学ぶのは今回で最後である。そこで、前期のまとめとしてこれまでに学んできたことを活かして、何か面白いまたは役に立つプログラム(report6.rb)を作成しよう(10点満点)。
これまで、if-endやwhile-end、値の型、配列、正規表現、openメソッドなど多くのことを学んできた。こうした様々な記法を出来る限り活用したプログラムを作成してみよう。
テーマが思いつかない場合、以下を参考にしてみよう。
- if-endで分岐していくシナリオ分岐型のゲーム:if-endとgetsとprintくらいしか使用しないので、ストーリーが面白くてもせいぜい8点
- 4択クイズ:これもif-endとgetsとprint、printfくらいで出来てしまうので8点くらい
- 高機能4択クイズ:問題や選択肢をあらかじめファイルに書いておきopenメソッドでそのファイルから読み込んで出題する。これだとopenメソッドや正規表現を使うので、9~10点
- コンピュータと対戦をするゲーム:簡単なものも難しいものも作れるので一概には言えないが、while-endや配列が利用できていれば9~10点。コンピュータの手を決めるために乱数を勉強する必要がある
プログラムを作成するに当たっては、第11回目のページや教科書を参考にしても良い。乱数発生は11回目のページで説明している。
作成したプログラムは第10回目の授業で自分のホームページに公開する。見られても恥ずかしくないプログラムを作成しよう。
提出要領
- 提出先:課題提出用メールアドレス
- 提出期限:第1次:6/10(水)23:00、第2次:6/17(水)23:00
- メールのSubject:report06
- 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降は下記の構成とする
- 作成したプログラムの概要(何をするプログラムか)
- 作成したプログラム
- プログラムの実行結果
- プログラムの説明
- 感想
- 参考文献
- 添付ファイル
採点要領
- 採点基準:期限内提出点(2点)、メールの体裁(1点)、プログラム(3点)、プログラムの説明(2点)、複雑さ(1点)、独自性(1点)
- 実行結果は複数回分貼りつけ、エラーがないことを明示すること。
- プログラムの説明:重要だと思うところを説明する。
- 複雑さ:if-endやwhile-end、配列、正規表現、openメソッドから3種類以上使用すること
- 独自性:他の人と異なるオリジナリティの高いプログラムを作ること
- わかりにくい説明や、Webページを単にコピー&ペーストしただけの説明は減点することがある。一度読み直してから提出すること。
- 他人のレポートを丸写しした場合は、写した側、写させた側共に0点とする。
- 驚異的に良くできているレポートについては満点を超える得点をつけることがある。
- よくできていたレポートは、他の人の参考になるよう、本人が特定できないような形で掲載する。掲載してほしくない場合はメールでの課題提出時にその旨記載すること。