これまでのプログラムでは、データの入力を行なうときはgets
メソッドを利用した。gets
は、プログラム起動時に引数を何も指定しないと端末(キーボード)から、引数にファイルを指定した場合そのファイルから1行ずつ内容を読む。
% ./test_grades.rb
(gets はキーボードからデータを読み込む)
% ./test_grades.rb scores.txt
(gets は scores.txt ファイルからデータを読み込む)
一方で、プログラムに与えるデータが予め決まっていて、常に一つの特定のファイルに保管されている場合などは、プログラムを実行する度にファイルを引数として与えなくても、そのファイルからデータを読み込む方法がある。
ファイルからデータを読み込む前に、先ずはファイルを開かなければいけない。そのために利用するのがopen
メソッドである。open
メソッドのは以下のように使われ、指定したファイルを指定したモードで開く。
open(ファイル, モード) do |変数|
開いたファイルを使って行う処理 (たとえば、変数.gets によってファイルからデータを読む)
end
「ファイル」には、オープンするファイルのパス名を指定する。プログラムが動作しているカレントディレクトリからの相対パスも、 ルートディレクトリからの絶対パスのどちらでも使用できる。オープンしたいファイルがカレントディレクトリ(プログラムと同じディレクトリ)に入っている場合は、ファイル名だけで十分。
「モード」には、ファイルに対して行いたい処理に応じて以下の文字列のうちのどれかを指定する。
"r"
("read"の省略)
読み込み専用モードで開く。
"r+"
読み書きモードで開く。ファイルを読み込む場合先頭から読み込み始める。
"w"
("write"の省略)
書き込み(新規作成/上書き)専用モードで開く。既存のファイルはクリアされる(空になる)。
"w+"
読み書きモードで開く。既存のファイルはクリアされる(空になる)。
"a"
("append"の省略)
追加書き込み専用モードで開く。ファイルが既に存在する場合はファイルの終わりから開始し、なければ新規作成する。
"a+"
読み書きモードで開く。ファイルが既に存在する場合はファイルの終わりから開始し、なければ新規作成する。
ファイルからの読み込みをするには、open
メソッドに読み込みしたいファイル名と、読み込むためのモード指定 "r"
を指定する。| |
の中の変数の名前は、そのファイルへのアクセスのときに使いたい任意のものにする。
open("phonelist.txt", "r") do |file|
while line = file.gets # file.gets で phonelist.txt を1行読み line に代入
…処理…
end
end
とすると、カレントディレクトリにある phonelist.txt
というファイルを読み込み専用モードで開き、開いた結果を変数 file
に入れる。このファイルから1行ずつデータを読むには、file
に備わっている gets
メソッドを使う。つまり、file.gets
とすることで、開いたファイルから1行ずつ読んだ結果が返ってくる。
前回のプログラムtotal_value.rb
を改良し、open
メソッドを使ってファイルを読み込みしてみよう。
total_value2.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
open("products.txt", "r") do |file|
while line = file.gets
if /(\S+)\s+(\d+)円\s+(\d+)/ =~ line
product = $1
price = $2.to_i
quantity = $3.to_i
total = price * quantity
printf("%sの総価格: %d\n", product, total)
end
end
end
結果:
sime{c11xxxx}% ruby total_value2.rb
パンの総価格: 9900
おにぎりの総価格: 1800
コーラの総合格: 31800
ボールペンの総価格: 23880
ファイルへの書き出しは、open
メソッドに、書き込むためのモード指定 "w"
または、"a"
いずれかを(用途に応じて)指定する。たとえば、以下のような処理となる。
open("grades.txt", "w") do |outfile|
…処理…
outfile.print "……書き出す文字列……"
…処理…
end
書き出しを行なう場合には、open
したときの変数に備わっている書き出し用メソッドを使う。今までのプログラムで、端末にメッセージを出力するときに利用していた、print, printf, puts
メソッドがそのまま使える。
たとえば、
x = 5*5
printf("結果は %d です\n", x)
とすると、
結果は 25 です
と出力されるが、
練習問題rslt2file.rb
x = 5*5
open("result.txt", "w") do |outfile|
outfile.printf("結果は %d です\n", x)
end
とすると、カレントディレクトリの result.txt
というファイルに
結果は 25 です
と出力される。また、オープンモードを "a"
にした場合、つまり
rslt2file2.rb
x = 6*6
open("result.txt", "a") do |outfile|
outfile.printf("結果は %d です\n", x)
end
とすると、result.txt
ファイルの末尾に追加され、ファイルの中身が以下のようになる。
結果は 25 です
結果は 36 です
配列の中身をファイルに保存したい時は、ループのなかで出力する。
練習問題array2file.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
products = ["パン", "チョコレート", "おにぎり"]
prices = [99, 150, 120]
filename = "prices.txt"
open(filename, "w") do |outfile|
outfile.puts "商品 価格"
i = 0
# ループの中で商品名と価格を出力する
while i < products.length do
outfile.printf("%s %d円\n", products[i], prices[i])
i += 1
end
end
実行後のファイルの中身:
商品 価格
パン 99円
チョコレート 150円
おにぎり 120円
printfなどの出力メソッドで特に出力先を指定しない場合は標準出力という特別なオブジェクトとなる。標準出力は STDOUT
という組み込み定数に結び付けられている。標準出力は最初は端末(ターミナル)に関連付けられている。標準出力はRubyプログラムの起動時に変えられていることもある。たとえば、
% ./program.rb > output.txt
とした場合は、標準出力は output.txt
ファイルに関連付けられるようになる。このように標準出力をファイルに替えることをリダイレクションという。
もう一つの出力用オブジェクトとして、エラーや警告を出力する時に使われる標準エラー出力がある。STDERR
という組み込み定数に結び付けられていて、最初は端末に関連付けられている。したがって、
STDERR.puts("100点以下の点数を入力してください!")
とした場合は、たとえ標準出力がファイルへリダイレクションされていたとしても、このメッセージの出力先は端末画面となる。
また、データを受け取るためのオブジェクトである標準入力は STDIN
という組み込み定数に結び付けられている。標準入力も最初は端末に関連付けられていて、キーボードから入力を受け取る。しかし、前回のようにRubyプログラムを起動するときに
% ./program.rb input.txt
とした場合、gets
メソッドは input.txt
からデータを読むため、標準入力から読めない。明示的に STDIN
を指定し STDIN.gets
とすることで読み取ることができる。
標準入出力について詳しく知りたい場合は、Unixひとめぐりページを参照してください。
open メソッドでは、ファイルだけでなく、
ことができる。この場合ファイル名の代わりに、|
(パイプ)記号とコマンドを結合したものを指定すればよい。たとえば、
ls.rb
open("| ls -l", "r") do |filelist|
while line = filelist.gets
puts line
end
end
とすると、ls -l
コマンド(ディレクトリ内のディレクトリやファイルの一覧を表示するコマンド)を実行した結果を1行ずつ読み込むことができる。また、
bc.rb
open("| bc -l", "w") do |calc|
calc.puts "9/2"
end
とすると、bc -l
コマンド(ターミナルで数値計算を行うためのコマンド)を起動し、その入力として
9/2\n
を送出する(putsは末尾に改行文字を追加して出力する)。
ファイル入出力の応用例として、決められた商品に対する、支払金額を計算するプログラムを作ろう。以下のような仕様のプログラムを作りたい。
以下のように、商品と価格の書かれたファイルがあったとする。
サーモン 110円
イカゲソ 110円
えんがわ 110円
マグロ 110円
ネギトロ 110円
蛸 110円
赤貝 110円
穴子 110円
鉄火巻 110円
カッパ巻 110円
ハマチ 110円
サヨリ 180円
中トロ 180円
牛トロ 180円
寒ブリ 180円
イクラ 240円
タラバ 240円
大トロ 360円
ウニ 360円
プログラムを起動すると、このファイルを自動的に読み込み、その内容をメニュー番号とともに標準出力に出す。
0 サーモン 110円
1 イカゲソ 110円
2 えんがわ 110円
3 マグロ 110円
4 ネギトロ 110円
5 蛸 110円
6 赤貝 110円
7 穴子 110円
8 鉄火巻 110円
9 カッパ巻 110円
10 ハマチ 110円
11 サヨリ 180円
12 中トロ 180円
13 牛トロ 180円
14 寒ブリ 180円
15 イクラ 240円
16 タラバ 240円
17 大トロ 360円
18 ウニ 360円
ユーザがメニュー番号を順次入力し終えるとそれまで入力したメニューの価格を全て足した金額を出力する。
全部で2490円でございます。
上記の動作イメージから分かることは、プログラムのおおまかな流れとして
sushi.txt
)を読み、品目と金額を全て記憶する。の3つがある。最後に合計金額を出力するので 2. のところで、つねに金額を足していなければならない。これらのことに注意して各段階の手順をプログラムにしていこう。
まず、sushi.txt
データファイルから価格リストを読み込み商品名を配列変数に入れる部分を作ろう。さらにその値段部分を別の配列変数に入れておこう。ここでは商品名を入れる配列を menu
変数、価格を入れる配列を prices
変数としよう。するとプログラムの該当部分は以下のようになる。
menu = []
prices = []
i = 0
open("sushi.txt", "r") do |file| # sushi.txtを読み込みモードで開く
while line = file.gets # ファイルから1行ずつ読む
if /(\S+)\s+(\d+)円/ =~ line # 「[空白文字以外の文字1つ以上][1つ以上の空白文字][数字]円」 というパターンがあれば
menu.push($1) # $1 で商品名を抽出して配列に追加
prices.push($2.to_i) # $2 で価格を抽出して整数化して配列に追加
printf("%3d %s\n", i, line.chomp) # 商品番号と、読み込んだ行を出力
i += 1
end
end
end
続いてユーザが対話入力で、商品番号を入れるループを作ろう。while true
は無限ループを作るので、何度でも注文できる。注文が終わったら "q"
を入力するとループが終了する。
while true # 無限ループ
print "御注文は(番号で入れてね、q で終了)?: "
line = gets.chomp # ユーザの入力を line に入れる
if line == "q" # "q" を入力したら
break # 終了
end
number = line.to_i # 整数(メニュー番号)にする
# メニュー番号に入っていない番号が入力された場合は再度入力させる
if number < 0 || number >= menu.length
puts "そんなメニューねぇでガス"
redo
end
# 注文された商品名を表示し合計金額にその値段を足す
printf("はあーい、「%s」一丁\n", menu[number])
sum += prices[number]
end
変数 sum
に入っている合計金額を出力して終了する。
print "おあいそでガス\n"
printf("全部で %d 円でガス。まいどっ\n", sum)
以上を組み合わせることでプログラムが完成する。
sushi.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
sum = 0
menu = []
prices = []
i = 0
open("sushi.txt", "r") do |file| # sushi.txtを読み込みモードで開く
while line = file.gets # ファイルから1行ずつ読む
if /(\S+)\s+(\d+)円/ =~ line # 「[空白文字以外の文字1つ以上][1つ以上の空白文字][数字]円」 というパターンがあれば
menu.push($1) # $1 で商品名を抽出して配列に追加
prices.push($2.to_i) # $2 で価格を抽出して整数化して配列に追加
printf("%3d %s\n", i, line.chomp) # 商品番号と、読み込んだ行を出力
i += 1
end
end
end
while true # 無限ループ
print "御注文は(番号で入れてね、q で終了)?: "
line = gets.chomp # ユーザの入力を line に入れる
if line == "q" # "q" を入力したら
break # 終了
end
number = line.to_i # 整数(メニュー番号)にする
# メニュー番号に入っていない番号が入力された場合は再度入力させる
if number < 0 || number >= menu.length
puts "そんなメニューねぇでガス"
redo
end
# 注文された商品名を表示し合計金額にその値段を足す
printf("はあーい、「%s」一丁\n", menu[number])
sum += prices[number]
end
print "おあいそでガス\n"
printf("全部で %d 円でガス。まいどっ\n", sum)
実行結果の例:
% ruby sushi.rb
御注文は(番号で入れてね、q で終了)?: 10
はあーい、「ハマチ」一丁
御注文は(番号で入れてね、q で終了)?: 13
はあーい、「牛トロ」一丁
御注文は(番号で入れてね、q で終了)?: q
おあいそでガス
全部で 290 円でガス。まいどっ
以下のファイルscores.txt
にさらに点数を追加したり、平均点数を計算したりできるプログラムtest_results.rb
を作成せよ。
プログラムを起動すると、3つの選択肢が表示される:
1 - 全ての点数を出力
2 - 点数を追加
3 - 平均点数を計算
0 - 終了
ユーザーが0を入力しない限り、何度でも1・2・3を選べるように、無限ループとbreak
を使うこと。
学籍番号 点数
C199000001 100
C199000002 80
C199000003 81
C199000004 84
実行結果の例:
{c11xxxx}% ruby ./test_results.rb
OPTIONS:
1 - 全ての点数を出力
2 - 点数を追加
3 - 平均点数を計算
0 - 終了
1
学籍番号 点数
C199000001 100
C199000002 80
C199000003 81
C199000004 84
OPTIONS:
1 - 全ての点数を出力
2 - 点数を追加
3 - 平均点数を計算
0 - 終了
3
平均点数: 86.25
OPTIONS:
1 - 全ての点数を出力
2 - 点数を追加
3 - 平均点数を計算
0 - 終了
2
学籍番号を入力してください
C199000005
点数を入力してください
100
点数が追加されました
OPTIONS:
1 - 全ての点数を出力
2 - 点数を追加
3 - 平均点数を計算
0 - 終了
1
学籍番号 点数
C199000001 100
C199000002 80
C199000003 81
C199000004 84
C199000005 100
OPTIONS:
1 - 全ての点数を出力
2 - 点数を追加
3 - 平均点数を計算
0 - 終了
3
平均点数: 89.00
OPTIONS:
1 - 全ての点数を出力
2 - 点数を追加
3 - 平均点数を計算
0 - 終了
0
本文は下記の通り記入してください.
氏名: 苗字名前
学籍番号: C11xxxxx
ソースコード:
...
実行結果:
...
発展課題
test_results.rb
を改良し、既存の点数を修正する機能を追加すること。sushi.rb
を、最後に注文された商品の一覧と注文金額を記載された領収書を receipt.txt
というファイルへ出力するように改良すること。