はじめに
IOオブジェクトを扱えるようになるとファイルから必要な情報を抽出したり、加工したりと作業効率化を測ることができます。
IOのあれこれ
IOクラスとは
ファイルに保存されたデータの処理など、プログラムの外部とデータをやりとりするための機能として入力(Input)と出力(Output)を提供します。
標準入出力とは
プログラムを起動すると、3つのIOオブジェクトがあらかじめ割り当てられています。
標準入力
標準入力はデータを受け取るためのIOオブジェクトです。組み込み定数
STDIN
に割り当てられているほか、グローバル変数$stdin
からも参照されています。レシーバを指定しないgets
などのメソッドは、$stdin
からデータを受け取ります。標準入力は最初はコンソールに関連づけられていて、キーボードの入力を受け取ります。
標準出力
標準出力はデータを出力するためのIOオブジェクトです。組み込み定数
STDOUT
に割り当てられているほか、グローバル変数$stdout
からも参照されています。レシーバを指定しないputs
、printf
などのメソッドは、$stdout
へ出力します。標準出力は最初コンソールに関連づけられています。
標準エラー出力
標準エラー出力は警告やエラー出力するためのIOオブジェクトです。組み込み定数
STDERR
に割り当てられているほか、グローバル定数$stderr
からも参照されています。警告メッセージを表示するためのwarn
メソッドが$stderr
へ出力します。標準エラー出力も最初はコンソールに関連づけられています。
標準入出力のプログラムでの挙動
$stdout.puts, $stderr.puts
3.times do |i|
$stdout.puts "#{Random.rand}"
$stderr.puts "#{i+1}回出力しました"
end
出力をファイルにリダイレクトすると、標準出力への書き込みはファイルに書き込まれ、標準エラー出力への書き込みのみが画面に表示されます。
$ ruby out.rb > log.txt
1回出力しました
2回出力しました
3回出力しました
log.txtの中身は次のようになっています。きちんと書き込まれていますね。
0.3583275954877376
0.6344095584909449
0.862767385471491
出力先を使い分けることで、プログラムの結果として必要なデータはファイルに保存しつつ、処理の進み具合をコンソールに表示することができるようになります。
$stdin.tty?
通常、標準入力・標準出力・標準エラー出力は、コンソールに関連づけられています。しかし、コマンドの出力をリダイレクトでファイルに落としたり、あるいはパイプ(|)を使ってほかのプログラムに渡す場合はそうではありません。IOオブジェクトがコンソールに関連づけられているかどうかは、tty?
メソッドで判別できます。
TTYという名称はテレタイプ端末(TeleTYpe)に由来している。
if $stdin.tty?
print "Stdin is a TTY. \n"
else
print "Stdin is not a TTY. \n"
end
$ ruby tty.rb
Stdin is a TTY.
$ echo | ruby tty.rb
Stdin is not a TTY.
File.open, open
下記のcsvファイルを開封し、ファイルを処理(加工)してみたいと思います。
id,"title","created_at","updated_at"
1,"3ステップでしっかり学ぶRuby入門","0000-00-00 00:00:00","0000-00-00 00:00:00"
2,"プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで","0000-00-00 00:00:00","0000-00-00 00:00:00"
3,"プログラミング言語Ruby","0000-00-00 00:00:00","0000-00-00 00:00:00"
4,"初めてのRuby","0000-00-00 00:00:00","0000-00-00 00:00:00"
5,"メタプログラミングRuby","0000-00-00 00:00:00","0000-00-00 00:00:00"
6,"たのしいRuby","0000-00-00 00:00:00","0000-00-00 00:00:00"
7,"Effective Ruby","0000-00-00 00:00:00","0000-00-00 00:00:00"
8,"たった1日で基本が身につくRuby on Rails超入門","0000-00-00 00:00:00","0000-00-00 00:00:00"
9,"基礎 Ruby on Rails","0000-00-00 00:00:00","0000-00-00 00:00:00"
10,"オブジェクト指向設計実践ガイド - Rubyでわかる 進化し続ける柔軟なアプリケーションの育て方","0000-00-00 00:00:00","0000-00-00 00:00:00"
このファイルの各行先頭20文字まで出力するコードは下記のようになります。
File.open('books.csv') do |file|
while line = file.gets # 行(ライン)が取得できる限り実行
p line.slice!(0..19) # 先頭20文字を破壊的に取得
end
end
$ ruby open.rb
"id,\"title\",\"created_"
"1,\"3ステップでしっかり学ぶRuby入"
"2,\"プロを目指す人のためのRuby入門"
"3,\"プログラミング言語Ruby\",\"0"
"4,\"初めてのRuby\",\"0000-0"
"5,\"メタプログラミングRuby\",\"0"
"6,\"たのしいRuby\",\"0000-0"
"7,\"Effective Ruby\",\""
"8,\"たった1日で基本が身につくRuby"
"9,\"基礎 Ruby on Rails\""
"10,\"オブジェクト指向設計実践ガイド "
このように、ファイルを開いて新しいFileオブジェクトを得るには、File.open
メソッドかopen
メソッドを使います。つまり、下記のコードに書き換えても同じです。
open('books.csv') do |file|
while line = file.gets
p line.slice!(0..19)
end
end
file.close, file.closed?
開いたファイルを閉じるにはclose
メソッドを使います。
1つのプログラムが同時に開くことのできるファイルの数には制限があるので、使い終わったあとはなるべく閉じるようにすべきです。
上のFile.open
メソッドを使い、ブロックを渡すと、使い終わったファイルを自動的に閉じることができます。また、このブロックを渡す書き方には、入出力操作の範囲がわかりやすくなるというメリットもあります。
file = File.open('books.csv')
p file.closed?
# => false
file.close
p file.closed?
# => true
File.read, File.binread
File.read
ではFileオブジェクトを作らずにfileからデータを読み込みます。
また、File.binread
ではfileをバイナリモードで開いて読み込みます。
text = File.read('books.csv')
p text
# => "id,\"title\",\"created_at\",\"updated_at\"\n1,\"3ステップでしっかり学ぶRuby入門\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n2,\"プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n3,\"プログラミング言語Ruby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n4,\"初めてのRuby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n5,\"メタプログラミングRuby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n6,\"たのしいRuby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n7,\"Effective Ruby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n8,\"たった1日で基本が身につくRuby on Rails超入門\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n9,\"基礎 Ruby on Rails\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n10,\"オブジェクト指向設計実践ガイド - Rubyでわかる 進化し続ける柔軟なアプリケーションの育て方\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n"
bintext = File.binread('books.csv')
p bintext
# "id,\"title\",\"created_at\",\"updated_at\"\n1,\"3\xE3\x82\xB9\xE3\x83\x86\xE3\x83\x83\xE3\x83\x97\xE3\x81\xA7\xE3\x81\x97\xE3\x81\xA3\xE3\x81\x8B\xE3\x82\x8A\xE5\xAD\xA6\xE3\x81\xB6Ruby\xE5\x85\xA5\xE9\x96\x80\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n2,\"\xE3\x83\x97\xE3\x83\xAD\xE3\x82\x92\xE7\x9B\xAE\xE6\x8C\x87\xE3\x81\x99\xE4\xBA\xBA\xE3\x81\xAE\xE3\x81\x9F\xE3\x82\x81\xE3\x81\xAERuby\xE5\x85\xA5\xE9\x96\x80 \xE8\xA8\x80\xE8\xAA\x9E\xE4\xBB\x95\xE6\xA7\x98\xE3\x81\x8B\xE3\x82\x89\xE3\x83\x86\xE3\x82\xB9\xE3\x83\x88\xE9\xA7\x86\xE5\x8B\x95\xE9\x96\x8B\xE7\x99\xBA\xE3\x83\xBB\xE3\x83\x87\xE3\x83\x90\xE3\x83\x83\xE3\x82\xB0\xE6\x8A\x80\xE6\xB3\x95\xE3\x81\xBE\xE3\x81\xA7\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n3,\"\xE3\x83\x97\xE3\x83\xAD\xE3\x82\xB0\xE3\x83\xA9\xE3\x83\x9F\xE3\x83\xB3\xE3\x82\xB0\xE8\xA8\x80\xE8\xAA\x9ERuby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n4,\"\xE5\x88\x9D\xE3\x82\x81\xE3\x81\xA6\xE3\x81\xAERuby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n5,\"\xE3\x83\xA1\xE3\x82\xBF\xE3\x83\x97\xE3\x83\xAD\xE3\x82\xB0\xE3\x83\xA9\xE3\x83\x9F\xE3\x83\xB3\xE3\x82\xB0Ruby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n6,\"\xE3\x81\x9F\xE3\x81\xAE\xE3\x81\x97\xE3\x81\x84Ruby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n7,\"Effective Ruby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n8,\"\xE3\x81\x9F\xE3\x81\xA3\xE3\x81\x9F1\xE6\x97\xA5\xE3\x81\xA7\xE5\x9F\xBA\xE6\x9C\xAC\xE3\x81\x8C\xE8\xBA\xAB\xE3\x81\xAB\xE3\x81\xA4\xE3\x81\x8FRuby on Rails\xE8\xB6\x85\xE5\x85\xA5\xE9\x96\x80\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n9,\"\xE5\x9F\xBA\xE7\xA4\x8E Ruby on Rails\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n10,\"\xE3\x82\xAA\xE3\x83\x96\xE3\x82\xB8\xE3\x82\xA7\xE3\x82\xAF\xE3\x83\x88\xE6\x8C\x87\xE5\x90\x91\xE8\xA8\xAD\xE8\xA8\x88\xE5\xAE\x9F\xE8\xB7\xB5\xE3\x82\xAC\xE3\x82\xA4\xE3\x83\x89 - Ruby\xE3\x81\xA7\xE3\x82\x8F\xE3\x81\x8B\xE3\x82\x8B \xE9\x80\xB2\xE5\x8C\x96\xE3\x81\x97\xE7\xB6\x9A\xE3\x81\x91\xE3\x82\x8B\xE6\x9F\x94\xE8\xBB\x9F\xE3\x81\xAA\xE3\x82\xA2\xE3\x83\x97\xE3\x83\xAA\xE3\x82\xB1\xE3\x83\xBC\xE3\x82\xB7\xE3\x83\xA7\xE3\x83\xB3\xE3\x81\xAE\xE8\x82\xB2\xE3\x81\xA6\xE6\x96\xB9\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n"
File.write(file, data)
Fileオブジェクトを作らずにfileにdataを書き込みます。
hi, takuyanin
text = "Hello, Takuya!"
File.write("takuyanin.txt", text)
p File.read("takuyanin.txt")
# => Hello, Takuya!
File.write("takuyanin.txt", "!", 5)
p File.read("takuyanin.txt")
# => Hello!, Takuya!
上記のように、第三引数に数値を指定すると、先頭からその数値バイト目以降に書き込み、その後のデータは残ります。
しかし、第3引数を省略した場合はファイルの内容を全てdataに置き換えます。
標準入出力の基本操作
eof?
gets
メソッドは入力の終わりに達してからさらに読み込むとnil
を返します。また、入力の終わりまで読み込んだかどうかを判定したいときは、eof?
メソッドで確認できます。
End Of Fileの略です。
open.rb
ファイルを次のように書き換えます。
File.open('books.csv') do |file|
while line = file.gets
p line
end
p file.eof?
end
$ ruby open.rb
"id,\"title\",\"created_at\",\"updated_at\"\n"
"1,\"3ステップでしっかり学ぶRuby入門\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n"
"2,\"プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n"
"3,\"プログラミング言語Ruby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n"
"4,\"初めてのRuby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n"
"5,\"メタプログラミングRuby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n"
"6,\"たのしいRuby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n"
"7,\"Effective Ruby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n"
"8,\"たった1日で基本が身につくRuby on Rails超入門\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n"
"9,\"基礎 Ruby on Rails\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n"
"10,\"オブジェクト指向設計実践ガイド - Rubyでわかる 進化し続ける柔軟なアプリケーションの育て方\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\"\n"
true
最後にtrue
が返ってきてます。
chomp!
chomp!
メソッドを使うと、文字列の末尾の改行文字を削除することができます。
File.open('books.csv') do |file|
while line = file.gets
p line.chomp!
end
end
$ ruby chomp.rb
"id,\"title\",\"created_at\",\"updated_at\""
"1,\"3ステップでしっかり学ぶRuby入門\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"2,\"プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"3,\"プログラミング言語Ruby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"4,\"初めてのRuby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"5,\"メタプログラミングRuby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"6,\"たのしいRuby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"7,\"Effective Ruby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"8,\"たった1日で基本が身につくRuby on Rails超入門\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"9,\"基礎 Ruby on Rails\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"10,\"オブジェクト指向設計実践ガイド - Rubyでわかる 進化し続ける柔軟なアプリケーションの育て方\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
each_line
each_line
メソッドは、IOオブジェクトの各行を取得し、処理を行います。
File.open('books.csv') do |file|
file.each_line do |line|
p line.chomp!
end
end
$ ruby each_line.rb
"id,\"title\",\"created_at\",\"updated_at\""
"1,\"3ステップでしっかり学ぶRuby入門\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"2,\"プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"3,\"プログラミング言語Ruby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"4,\"初めてのRuby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"5,\"メタプログラミングRuby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"6,\"たのしいRuby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"7,\"Effective Ruby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"8,\"たった1日で基本が身につくRuby on Rails超入門\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"9,\"基礎 Ruby on Rails\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"10,\"オブジェクト指向設計実践ガイド - Rubyでわかる 進化し続ける柔軟なアプリケーションの育て方\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
each
readline
メソッドを使って一気に終わりまで読んで、各行を要素とする配列を取得し、each
メソッドで加工を施しています(下記の例では、改行文字も削除)。
File.open('books.csv') do |file|
ary = file.readlines
ary.each do |line|
p line.chomp!
end
end
$ ruby each.rb
"id,\"title\",\"created_at\",\"updated_at\""
"1,\"3ステップでしっかり学ぶRuby入門\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"2,\"プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"3,\"プログラミング言語Ruby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"4,\"初めてのRuby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"5,\"メタプログラミングRuby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"6,\"たのしいRuby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"7,\"Effective Ruby\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"8,\"たった1日で基本が身につくRuby on Rails超入門\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"9,\"基礎 Ruby on Rails\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
"10,\"オブジェクト指向設計実践ガイド - Rubyでわかる 進化し続ける柔軟なアプリケーションの育て方\",\"0000-00-00 00:00:00\",\"0000-00-00 00:00:00\""
lineno
gets
メソッドやeach_line
メソッドを使って、行単位で読み込みを行うと、それまでに何行読み込んだかが自動的に記録されます。その行数はlineno
メソッドで取得できます。
File.open('books.csv') do |file|
ary = file.readlines
p "#{file.lineno}行読み込みました"
end
$ ruby lineno.rb
"11行読み込みました"
books.csvファイルの中身は11行だったので、linenoメソッドの挙動が確認できますね。
また次の例も見ていただくとさらにlineno
メソッドの理解が進むかと思います。
File.open('books.csv') do |file|
file.each_line do |line|
printf("%03d %s", file.lineno, line)
end
end
$ ruby lineno2.rb
001 id,"title","created_at","updated_at"
002 1,"3ステップでしっかり学ぶRuby入門","0000-00-00 00:00:00","0000-00-00 00:00:00"
003 2,"プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで","0000-00-00 00:00:00","0000-00-00 00:00:00"
004 3,"プログラミング言語Ruby","0000-00-00 00:00:00","0000-00-00 00:00:00"
005 4,"初めてのRuby","0000-00-00 00:00:00","0000-00-00 00:00:00"
006 5,"メタプログラミングRuby","0000-00-00 00:00:00","0000-00-00 00:00:00"
007 6,"たのしいRuby","0000-00-00 00:00:00","0000-00-00 00:00:00"
008 7,"Effective Ruby","0000-00-00 00:00:00","0000-00-00 00:00:00"
009 8,"たった1日で基本が身につくRuby on Rails超入門","0000-00-00 00:00:00","0000-00-00 00:00:00"
010 9,"基礎 Ruby on Rails","0000-00-00 00:00:00","0000-00-00 00:00:00"
011 10,"オブジェクト指向設計実践ガイド - Rubyでわかる 進化し続ける柔軟なアプリケーションの育て方","0000-00-00 00:00:00","0000-00-00 00:00:00"
printf
メソッドについて(format
など)は下記記事にまとめております。
format引数でよく見かける'%010d'の実態を解明[Ruby]
each_char
each_char
メソッドはIOオブジェクトから1文字ずつデータを読み込んでブロックを実行します。
File.open('books.csv') do |file|
file.each_char do |ch|
p ch
end
end
$ ruby each_char.rb
"i"
"d"
","
"\""
"t"
"i"
.
.
.
each_byte
IOオブジェクトから1バイトずつデータを読み込んでブロックを起動します。ブロック変数には、読み込んだバイトに対応するASCIIコード
を整数値で渡します。
File.open('books.csv') do |file|
file.each_byte do |ch|
p ch
end
end
$ ruby each_byte.rb
105
100
44
34
116
.
.
.
getc
IOオブジェクトからデータを1文字だけ読み込みます。ファイルのエンコーディングによっては1文字は複数のバイトから構成される場合もありますが、1文字分を読み込んでStringオブジェクトを返します。入力の終わりに達してからさらに読み込むと、nil
を返します。
File.open('books.csv') do |file|
while ch = file.getc
p ch
end
end
$ ruby getc.rb
"i"
"d"
","
"\""
"t"
"i"
.
.
.
ungetc
引数で指定した文字をIOオブジェクトの入力バッファに戻します。
less takuyanin.txt
Hello! Takuya!
File.open('takuyanin.txt') do |io|
p io.getc
io.ungetc("h")
p io.gets
end
$ ruby open.rb
"H"
"hello! Takuya!"
おわりに
takuyaninのマイページにRails, Rubyに関していくつか記事をまとめていますので、よければご参考ください。
この記事が役に立ったいう方は、いいね、お願いします(^^)