まさ@ブログ書き込み中

自由に生きるための英語、プログラミング、考えごとについて色々書いています。

I/Oクラスを調べてみた

 

こんにちは、まさです。

mwedアドベントカレンダーの2日目の記事として書いていきたいと思います。

 

1日目は和馬がウェディングにちなんだ遊び心満載のブログを書いてくれました。

blog.kazumalab.com

 

ですが、僕は全くウェディングに関係ない最近気になっていたRubyのI/Oクラスについて調べて書いていきたいと思います。和馬、ごめん。

 

 

I/Oクラスが気になる

最近開発しているシステムではxlsxファイルをRubyのデータとして扱うようにしたり、csvに変換したり、PDFファイルを分割してpngファイルにしたり、データからxlsxファイルを出力したりするといった機能を作ったり、触れたりすることがありました。

 

また、最近あったみんなのウェディング出張インターン at 琉球大学はWebサーバを作るというテーマで標準入出力を利用するシーンもありましたが、僕は$stdinとか$stdoutについて良くわかっていませんでした。これまでISUCONのことを学んだりWebサービスを作る上では出会わなかったもので・・・。

 

というわけで、今回を機にRubyのI/Oクラスについてざっと学んでみたいと思います。

 

 

I/Oクラスってなに

I/Oクラスは文字通り「プログラムの外部とデータのやりとりをするための機能として入力(Input)と出力(Output)を提供する」クラスだそうです。

 

 

標準入出力

標準入出力、エラー出力に対応するIOオブジェクトはRubyでは定数に格納されているそうな。それぞれstdin, stdiout, stderrを大文字、またはそれぞれの頭に「$」をつけたものらしい。例えば標準入力を表す定数は$stdinまたはSTDINです。

 

標準入力はキーボードからの入力を受け付け、標準出力の書き込みは基本端末画面だそうです。puts、print、printfなどは標準出力への出力となっているそうです。

 

また、標準入力では最後に改行文字が入るみたいですな。

 

例えば、以下のようなコードを書いた場合、

# 標準入力
input = $stdin.gets

puts "-------------"

 

# 変数inputの中身を見る
p input

 

puts "-------------"

 

# 標準出力
puts "Hello, #{input.chomp!}!"

 

ターミナルではこのようになります。

f:id:masaincebu:20171202125402p:plain

 

ただ、このIOオブジェクトを省略しても標準入力はできるみたいです。こんな感じで。

p '文字を入力してください。'
input = gets
p input

 

これは標準入出力は Ruby の起動時に IO オブジェクトが自動的に生成されるからみたいです。通常のファイルは IO オブジェクトを生成しないとアクセスすることはできないそうな。

 

 

IOクラスのインスタンスメソッド

書き込み(出力)

mwedインターンの資料では以下のような形で標準入出力が使われていました(一部変更しています)。

def start
 service($stdin, $stdout)
end

 

def service(stdin, stdout)
 stdout.write stdin.chomp! + "yoyo"
end 

 

標準入出力は明示的にであれ暗黙的にであれ$stdin、$stdoutなどのIOオブジェクトを介して実現します。

 

ここではstartメソッドの引数としてIOオブジェクトが渡され、その内部で呼び出されたserviceメソッドでは標準出力のオブジェクトがwriteメソッドを呼び出しています。

 

writeメソッドは指定の文字列を出力するメソッドみたいです。文字列でなければ文字列に変換するそうな。

 

ファイル開閉

こんな感じみたい。

ファイルを開く
  • io = File.open(file, mode)
  • io = open(file, mode)

ファイルを閉じる

  • io.close

 

「1つのプログラムが開くことができるファイル数には制限がある」そうなので、closeメソッドも大事なのですね。

 

その他いろいろ

他にも色々なメソッドがあるみたい。

# 全部読み込んで、一気に出力してくれる
puts STDIN.read

 

# 最初の一行だけ読み込んで出力してくれる
puts STDIN.gets

 

# 改行文字で区切って処理してくれる
STDIN.each_line {|line|
puts line
}

 

# 要素ごとに処理してくれる
STDIN.map {|data|
puts data
}

 

 

csvファイルを扱ってみる

色んなファイルをRubyのIOクラスでがちゃがちゃしてきたのですが、いまだに色んなファイルをRuby内のデータとして扱えるのが不思議でならなかったりします。

 

今回はcsvファイルをRubyの標準ライブラリcsvで扱ってみようかと思います。以前開発で使ったときはrooというライブラリを使っていたので、使い心地は如何なものかと。

github.com

 

データを用意する

data.csvに以下のようなcsvデータを用意しました。

1,masa,500
2,toku,400
3,moto,300

 

ファイルを読み込む

たったこれだけで・・・?

require 'csv'

# ファイルから一行ずつ読み込んで出力
CSV.foreach("./data.csv") do |row|
 p row
end

 

結果

["1", "masa", "500"]
["2", "toku", "400"]
["3", "moto", "300"]

 

こういったやり方もできるみたい。

require 'csv'

# ファイルから一度に読み込み出力する
arr_of_arrs = CSV.read("./data.csv")

p arr_of_arrs

 

結果

[["1", "masa", "500"], ["2", "toku", "400"], ["3", "moto", "300"]]

 

ファイルに書き込む

data.csvのデータをいじってdata2.csvに書き込んでみます。

 

require 'csv'

# 行ごとのCSVデータを配列に入れる
arr_of_arrs = CSV.read("./data.csv")

 

# ファイルへ書き込み
CSV.open("./data2.csv", "wb") do |csv|
 arr_of_arrs.each do |arr|
  csv << [arr[0],arr[1],arr[2] + "00"]
 end
end

 

結果

data2.csv

1,masa,50000
2,toku,40000
3,moto,30000

 

うまくいきましたー。ファイル書き込みのコードの1行目の"wb"とはバイナリ書き込みモード(write + binaryかな)の意味で、バイナリモードにすればIOのエンコーディングの影響を受けないみたいです。

 

 

標準ライブラリとgem

今回はI/Oクラスについて色々基本的なことをまとめてみたり、標準ライブラリcsvを使ってみたりしました。

 

ちょこっと使った感触では全然csvを扱うならこのライブラリで良さそうでした。csvだけでなく、xlsxファイルも扱う必要があるなら断然rooな気がしています。

 

I/Oクラスについて調べたせいか、標準ライブラリを使うことは「ライブラリ」とは言ってもGemと比べてまだRubyの基本に沿っている感覚があって良いと思いました。魔法を使っている感じが比較的少ない気がするんですよね。

 

僕のまとめはここまでです。あー楽しかった!

 

明日は@kgymiさんが「『小さく試して改善を繰り返す』新しいサービスを広げるためのデザインアプローチ」について書いてくれるみたいです!楽しみ!

 

参照