IOクラスについて($stdin, $stdout, $stderr, etc..)[Ruby]

# **はじめに**
IOオブジェクトを扱えるようになるとファイルから必要な情報を抽出したり、加工したりと作業効率化を測ることができます。


# **IOのあれこれ**
## **IOクラスとは**
ファイルに保存されたデータの処理など、プログラムの外部とデータをやりとりするための機能として入力（Input）と出力（Output）を提供します。

## **標準入出力とは**
プログラムを起動すると、3つのIOオブジェクトがあらかじめ割り当てられています。

### **標準入力**
> 標準入力はデータを受け取るためのIOオブジェクトです。組み込み定数`STDIN`に割り当てられているほか、グローバル変数`$stdin`からも参照されています。レシーバを指定しない`gets`などのメソッドは、`$stdin`からデータを受け取ります。標準入力は最初はコンソールに関連づけられていて、キーボードの入力を受け取ります。

### **標準出力**
> 標準出力はデータを出力するためのIOオブジェクトです。組み込み定数`STDOUT`に割り当てられているほか、グローバル変数`$stdout`からも参照されています。レシーバを指定しない`puts`、`print`、`printf`などのメソッドは、`$stdout`へ出力します。標準出力は最初コンソールに関連づけられています。

### **標準エラー出力**
> 標準エラー出力は警告やエラー出力するためのIOオブジェクトです。組み込み定数`STDERR`に割り当てられているほか、グローバル定数`$stderr`からも参照されています。警告メッセージを表示するための`warn`メソッドが`$stderr`へ出力します。標準エラー出力も最初はコンソールに関連づけられています。

## **標準入出力のプログラムでの挙動**

### **$stdout.puts, $stderr.puts**

```out.rb
3.times do |i|
  $stdout.puts "#{Random.rand}"
  $stderr.puts "#{i+1}回出力しました"
end
```
出力をファイルにリダイレクトすると、**標準出力への書き込みはファイルに書き込まれ、標準エラー出力への書き込みのみが画面に表示されます**。

```
$ ruby out.rb > log.txt
1回出力しました
2回出力しました
3回出力しました
```
log.txtの中身は次のようになっています。きちんと書き込まれていますね。

```log.txt
0.3583275954877376
0.6344095584909449
0.862767385471491
```
**出力先を使い分けることで、プログラムの結果として必要なデータはファイルに保存しつつ、処理の進み具合をコンソールに表示することができるようになります**。

### **$stdin.tty?**
通常、標準入力・標準出力・標準エラー出力は、コンソールに関連づけられています。しかし、**コマンドの出力をリダイレクトでファイルに落としたり、あるいはパイプ（｜）を使ってほかのプログラムに渡す場合はそうではありません**。IOオブジェクトがコンソールに関連づけられているかどうかは、`tty?`メソッドで判別できます。
TTYという名称はテレタイプ端末（TeleTYpe）に由来している。

```tty.rb
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ファイルを開封し、ファイルを処理（加工）してみたいと思います。

```books.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"
```

このファイルの各行先頭２０文字まで出力するコードは下記のようになります。

```open.rb
File.open('books.csv') do |file|
  while line = file.gets # 行（ライン）が取得できる限り実行
    p line.slice!(0..19)　 # 先頭２０文字を破壊的に取得
  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.rb
open('books.csv') do |file|
  while line = file.gets
    p line.slice!(0..19)
  end
end
```

### **file.close, file.closed?**
開いたファイルを閉じるには`close`メソッドを使います。
1つのプログラムが同時に開くことのできるファイルの数には制限があるので、使い終わったあとはなるべく閉じるようにすべきです。
上の`File.open`メソッドを使い、ブロックを渡すと、使い終わったファイルを自動的に閉じることができます。また、このブロックを渡す書き方には、入出力操作の範囲がわかりやすくなるというメリットもあります。

```close.rb
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をバイナリモードで開いて読み込みます。

```read.csv
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を書き込みます。

```takuyanin.txt
hi, takuyanin 
```

```write.rb
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!
```
上記のように、第三引数に数値を指定すると、先頭からその数値バイト目以降に書き込み、その後のデータは残ります。
しかし、**第３引数を省略した場合はファイルの内容を全てdataに置き換えます**。

## **標準入出力の基本操作**

### **eof?**
`gets`メソッドは入力の終わりに達してからさらに読み込むと`nil`を返します。また、入力の終わりまで読み込んだかどうかを判定したいときは、`eof?`メソッドで確認できます。
End Of Fileの略です。

`open.rb`ファイルを次のように書き換えます。

```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!`メソッドを使うと、文字列の末尾の改行文字を削除することができます。

```chomp.rb
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オブジェクトの各行を取得し、処理を行います。

```each_line.rb
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`メソッドで加工を施しています（下記の例では、改行文字も削除）。

```each.rb
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`メソッドで取得できます。

```lineno.rb
File.open('books.csv') do |file|
  ary = file.readlines
  p "#{file.lineno}行読み込みました"
end
```

```
$ ruby lineno.rb
"11行読み込みました"
```
books.csvファイルの中身は11行だったので、linenoメソッドの挙動が確認できますね。

また次の例も見ていただくとさらに`lineno`メソッドの理解が進むかと思います。

```lineno2.rb
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]](https://qiita.com/takuyanin/items/3b210f166f24c6d13e5a)

###**each_char**
`each_char`メソッドはIOオブジェクトから1文字ずつデータを読み込んでブロックを実行します。

```each_char.rb
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コード`を整数値で渡します。

```each_byte.rb
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`を返します。

```getc.rb
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!
```

```ungetc.rb
File.open('takuyanin.txt') do |io|
  p io.getc
  io.ungetc("h")
  p io.gets
end
```

```
$ ruby open.rb
"H"
"hello! Takuya!"
```

#**おわりに**
[takuyaninのマイページ](https://qiita.com/takuyanin)にRails, Rubyに関していくつか記事をまとめていますので、よければご参考ください。

この記事が役に立ったいう方は、いいね、お願いします(^^)