roy > naoya > 基礎プログラミングI·情報検索 > (9)ファイルの入出力

(9) 06/25の授業内容:ファイルの入出力

openメソッド

前回の課題(kadai6.rb)はgetsメソッドを使って外部ファイルから1行ずつ読み込み特定の処理をするものであった。読み込むファイルの指定は、ktermでプログラムを実行する際に

irsv{naoya}% ruby kadai6.rb work.txt

というように、実行するプログラム名の後ろに記述した。これにより指定した任意のファイルを自由に読み込むことが可能となった。

しかし、こうした指定方法にはデメリットもある。

  • ファイル名の入力間違いが生じる可能性がある
  • ファイル名を指定し忘れる可能性がある
  • 常に同じファイルを用いるのであれば、いちいち指定するのがわずらわしい

これらの問題に対処するために準備されているのがopenメソッドである。openメソッドを使うとプログラム内でファイルオープンやクローズを行うことができる。

openメソッドの基本構造

open("ファイル","モード")do |変数|
    開いたファイルを使って行う処理
end

ファイル:ファイル名を指定する。ファイルとプログラムは同一のディレクトリに置く。

モード:指定したファイルを開いて読み込むのか、指定したファイルを作成して書き込むのか、既存のファイルを開いて追加書き込みするのか等、モードを指定する。以下の6種類から選ぶ。

  1. r:読み込み専用。
  2. r+:読み込み/書き込用。
  3. w:書き込み専用。指定したファイルを新規作成する(既存のファイルを指定した場合は自動的に削除され、新規作成される)。
  4. w+:読み込み/書き込み用で、その他はwと同じ。
  5. a:追加書き込み専用。指定ファイルが存在しない場合は新規作成し、既存ファイルを指定した場合はデータの末尾に続けて書き込みを行う。
  6. a+:読み込み/追加書き込み専用で、その他はaと同じ。

変数:printfやprintメソッドの表示先、getsメソッドの読み込み元を指定する際に使用する。任意の名前でよい。

openメソッドを使ったファイルからの読み込み

前回の授業で取り上げた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での実行時にはファイル名の指定はしていない。

irsv{naoya}% 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メソッドはディスプレイとファイルに出力することができる。したがって、どこから読み込みどこに出力するのかを指定しなければ、適切に動作をすることができなくなる。

読み込み元や読込先が複数ある場合の対応

  • キーボードからの読み込みSTDIN.gets
  • ファイルからの読み込み変数.gets(上記のプログラムではtest.gets)
  • ディスプレイへの表示STDERR.print、STDERR.printf
  • ファイルへの出力変数.print、変数.printf(上記のプログラムでは、test.print、test.printf)

openメソッドを使ったファイルへの書き込み

今度は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)に出力するものである。

irsv{naoya}% 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 ファイル名でそのファイルの中身を見ることができる。

irsv{naoya}% cat data2.txt[Return]
学籍番号         点数
c1080014          45
c1080026          68

ファイルの中身を確認すると、open.rbで読み込みに使用したdata.txtと同一の構造をとっていることがわかる。つまり、このプログラムでopen.rbで使用するデータを作成することができる。

若干繰り返しになるが、openメソッドに関連する部位について確認しよう。

open ("data2.txt", "a") 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としている。

open.rbとopen2.rbを合体しよう

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 = 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点。提出要領は下記の通り。

  • 提出先:naoya@e.koeki-u.ac.jp
  • メールのSubject:ruby09
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降はcatコマンドで確認したdata2.txtを貼りつける。最低でも4回実行するので4回分(以上)貼りつける。その後わかったことを記載する。

Tips:emacsでの日本語入力のオンオフはCtrl-oです

Tips:Mewによるメールの送り方はMewコマンドを参照

レポート課題

以下の4種類の課題から実施できそうなものどれか1つを選んで実施する。採点は以下の通り行う。

  • 1番:8点満点で採点
  • 2番:6点満点で採点
  • 3番:8点満点で採点
  • 4番:10点満点で採点
  • 5番:12点満点で採点
  1. これまでに作成したプログラムの中から1つを選び、自分のWebページで公開する。htmlの文法の正しさに基づき採点する。なお作成するのはhtmlファイル1つであり、その中にプログラムの概要とプログラムを記載する。デザインや概要の文章を工夫して、プログラムを実行してみたくなるようなページ作りを心がけること。なお、htmlファイル作成にあたってはtext-decorationやline-heightなどのCSSのプロパティを最低でも5種類は使用すること。
  2. kadai6.rbをopenメソッドを使って書き直す。
  3. kadai6.rbで使用したwork.txtを出力するプログラムを作成し、open_final.rbのように入力モードと分析モードを持つ単一のプログラムとする。つまり、kadai6.rbが分析モード、work.txtを出力する今回新規作成するプログラムが入力モードとなる。分析モードでは結果をktermに出力する。openメソッドを使うこと。kadai6.rbは前回の1番を使用する。
  4. 3番のプログラムにさらに明細発行モードを追加し、kadai4.rbで作成した明細票を発行できるようにする。明細票は4人分を一度に発行しても良いし、指定した1人分だけ発行できるようにしても良い。なお、明細票はファイルに出力されるようにすること。kadai6.rbは前回の1番を使用する。
  5. 3番のプログラムにさらに明細発行モードを追加し、kadai4.rbで作成した明細票を発行できるようにする。明細票は4人分を一度に発行しても良いし、指定した1人分だけ発行できるようにしても良い。なお、明細票はファイルに出力されるようにすること。kadai6.rbは前回の2番をベースにする。

3番や4番、5番のプログラムを作成する場合、利用者の使い方として、入力モードは毎日の営業終了後、分析モードは任意のタイミング、明細発行モードは月末(月末締めとする)となる。kadai6.rbの1番は任意のタイミングで使用できるが、kadai6.rbの2番は20日経過後でないと正しく動かないためこのままでは都合が悪い。

5番を行う場合、残り日数が何日であるのか、残り日数の中に土日が何日あるのかをユーザに入力させるのではなく、プログラム内で自動的に調べられるようにすること。また6月だけでなく任意の月に利用できるようにすること。ただし今回はうるう年は考えなくてよいものとする。

5番のヒント:あらかじめカレンダーを外部ファイルで作成しておき、それを読み込むとよい。x月は30日まであり、1日は木曜日などという情報がわかれば、あとは任意のタイミングで分析モードを使用しても、残り日数とその中に土日が何日あるか求められるはずである。また、この機能をうまく使えば入力モードで毎回日付と曜日を入力しなくてもよいことになり、利用者の負担を軽減させることも可能となる。

  • 提出先:naoya@e.koeki-u.ac.jp
  • 提出期限:7/1(日)23:59
  • メールのSubject:kadai07
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目に何番を選んだかを記載し、3行目以降は下記の構成とする

1番の場合

  1. 作成したページのURL
  2. 感想

2〜5番の場合

  1. 作成したプログラム
  2. プログラムの実行結果
  3. 生成されたファイル(3番or4番or5番の場合のみ。4番と5番は入力モード、明細発行モードでファイルが生成されるのでいずれも貼り付ける)
  4. プログラムの説明
  5. 感想

  • 採点基準(1番):期限内提出点(2点)、メールの体裁(1点)、CSS・デザイン(2点)、htmlの構文(3点):htmlの構文はAnother HTML-lint gatewayで採点し、マイナスなら0点、50点以下なら1点、100点未満なら2点、100点なら3点とする。
  • 採点基準(1番以外):期限内提出点(2点)、メールの体裁(1点)、プログラム(1点||3点||4点||6点)、プログラムの説明(2点||2点||3点||3点)
  • プログラムの説明(2番):変更箇所のみ説明すればよい。
  • プログラムの説明(3番or4番or5点):重要だと思うところを説明する。
  • わかりにくい説明や、Webページを単にコピー&ペーストしただけの説明は減点することがある。一度読み直してから提出すること。
  • 驚異的に良くできているレポートについては満点を超える得点をつけることがある。
  • よくできていたレポートは、他の人の参考になるよう、本人が特定できないような形で掲載する。掲載してほしくない場合はメールでの課題提出時にその旨記載すること。

Tips:emacsでの日本語入力のオンオフはCtrl-oです

Tips:ktermでのプログラムの実行結果をメールに貼り付けるには、コピーしたい箇所をマウスで選択し、emacs(Mew)上でマウスの真ん中ボタンをクリックする

Tips:Mewによるメールの送り方はMewコマンドを参照