Ruby IOクラスについて学ぶ

Ruby力をつけるための基礎固めとして、IOクラスについて整理します。※たのしいRubyから

# IOクラスとは

> プログラムの外部とデータのやりとりをするための機能として入力(Input)と出力(Output)を提供するのが、IOクラスです
>
> | たのしいRuby P339

## 標準入出力 3つのIOオブジェクト
### 標準入力
- キーボードからの入力を受け付ける
- 組み込み定数STDINに割り当てられる
- getsなどで標準入力からデータを受け取る

### 標準出力
- 標準出力の書き込みは端末画面に向けられている
- 組み込み定数STDOUTに割り当てられる
- レシーバを指定しないputs、print、printfなどは標準出力への出力となる

### 標準エラー出力
- 標準エラー出力の書き込みは端末画面に向けられている
- 組み込み定数STDERRに割り当てられる

標準入力が端末からかどうか判定できる

```rb
if $stdin.tty?
  print "tty true"
else
  print "tty false"
end
```
↓

```sh
$ ruby tty.rb
=> "tty true"

$ echo | ruby tty.rb
=> "tty false"
```

## ファイル開閉操作

### ファイルを開いて新しいIOオブジェクトを得る
- *io* = **File.open**(*file, mode*)
- *io* = **open**(*file, mode*)

modeはどのような目的で開くかを指定する (たのしいRuby P343 表17.1)

| mode | 意味 |
|:-----:|:----|
|"r"|読込専用ファイル|
|"r+"|読込/書き込み用|
|"w"|書き込み専用。ファイルがなければ新規作成、既に存在する場合はファイルサイズを0にする|
|"w+"|読込/書き込み用。その他は"w"と同じ|
|"a"|追加書き込み専用。ファイルがなければ新規作成|
|"a+"|読込/追加書き込み専用。ファイルがなければ新規作成|

### 開いたファイルを閉じる
- *io*.**close**

１つのプログラムが開くことができるファイル数には制限があるので、なるべく閉じましょう

#### File.open + ブロック
File.open に ブロックを渡すと自動的に閉じることができる

```rb
File.open("foo.txt") do |io|
  while line = io.gets
    p line
  end
end
```

### IOオブジェクトが閉じられてからどうか確認する
- *io*.**closed**

### ファイルのデータを一度に読み込む
- **File.read**(*file*)

ガバっといっきに読み込みます

## ファイルの入出力操作
### ■ 入力操作
#### IOクラスオブジェクトからデータを1行読み込む
- *io*.**gets**(*rs*)
- *io*.**each**(*rs*)
- *io*.**each_line**(*rs*)

行の区切りは引数(rs)で指定することができる(デフォルトは"¥n")

返却値には改行文字列を含みます

**gets**メソッドは入力終わりに達してから更に読込とnilを返す

入力終わりまで言ったかどうかは **eof?** メソッドで判定

```rb
while line = io.gets
  line.chomp! # 改行文字を削除
  puts line
end
```

```rb
io.each_line do |line|
  line.chomp! # 改行文字を削除
  puts line
end
```

#### 改行区切りの配列として返却
- *io*.**readlines**(*rs*)

```rb
io.readlines
=> ["aaaa\n", "bbbb\n", "cccc\n", "dddd\n", "eeee\n", "ffff\n", "gggg\n"]
```

#### 何行まで読み込んだか取得
- *io*.**lineno**
- *io*.**lineno**=(*number*)

```rb
file = File.open("io_sample.txt", "r")
file.each_line do | line |
  p file.lineno
  p line
end
=>
1
"aaa\n"
2
"bbb\n"
3
"ccc\n"
4
"aaa\n"
5
"bbb\n"
6
```

**lineno=**メソッドで値を変更できるが変更してもファイルのポインタは変わりません

#### 1文字ずつ読込みブロックを起動
- *io*.**each_char**

```rb
io.each_char do |ch|
  p ch
end
```

#### 1バイトずつ読込みブロックを起動
- *io*.**each_byte**

#### 1文字だけ読込む
- *io*.**getc**

```rb
file.getc
=> "a"
file.getc
=> "\n"
file.getc
=> "b"
file.getc
=> "\n"
file.getc
=> "c"
file.getc
=> "\n"
file.getc # 末尾まで行くとnil
=> nil
```

#### 指定した1文字分を入力バッファへ戻す
- *io*.**ungetc**

#### 1バイトだけ読込む
- *io*.**getbyte**

1バイトだけ読み込んで、バイトに対応するアスキーコードの整数オブジェクトを返却する

#### 指定した1バイト分を入力バッファへ戻す
- *io*.**ungetbyte**

```rb
file.getbyte
=> 97
file.ungetbyte(97)
=> nil
file.getbyte
=> 97
```

#### 一気に読み込む (size指定して読み込む)
- *io*.**read**

### ■ 出力操作
#### 文字列に改行文字を補って出力
- *io*.**puts**(*str0, str1, ...*)

```rb
file = File.open("io_sample.txt", "r+")
file.puts("hoge", "fuga")

----
cat io_sample.txt
=>
hoge
fuga
aaa
bbb
ccc
```

#### 指定した文字コードに対応する文字を出力
- *io*.**putc**(*ch*)

```rb
$stdout.putc(99)
=> c
$stdout.putc(81)
=> Q
$stdout.putc(82)
=> R
$stdout.putc("gaga")
=> g
```

#### 引数に指定した文字列を出力
引数がStringオブジェクト以外は文字列に変換する

- *io*.**print**(*str0, str1, ...*)

#### 書式指定で出力
- *io*.**printf**(*format, arg0, arg1, ...*)

```rb
$stdout.printf("%s-%d", "ruby", 17)
=> ruby-17
```

#### 指定の文字列を出力 (1)

引数がStringオブジェクト以外は文字列に変換する
また書き込んだバイト数を返却する

- *io*.**write**(*str*)

```rb
byte_size = $stdout.write(String)
=> String
p byte_size
=> 6
```

#### 指定の文字列を出力 (2)

戻り値はレシーバ自身

- *io* **<<** *str*

```rb
$stdout << 'foo' << 'hoge' << 'hage'
=> foohogehage
```

## ファイルポインタ
> IOオブジェクトがファイルのどこを指しているか示す情報 ~ 中略 ~
ファイルポインタは読み書きを行うたびに自動的に進みますが、自分で操作すると、ファイルの中の好きな位置のデータを読み書きできる
 | たのしいRuby P350

#### 現在のファイルポインタを取得
- *io*.**pos**
- *io*.**pos**=(*position*)

pos=メソッドでファイルポインタの位置を変更できる。

#### ファイルポインタを移動する

- *io*.**seek**(*offset*, *whence*)

引数*offset*には位置を整数で指定する。
引数*whence*にはoffsetをどう評価するかを指定する。
■ whenceの値
- IO::SEEK_SET : offsetで指定された位置にファイルポインタを移動する
- IO::SEEK_CUR : offsetを現在の相対位置とみなしてファイルポインタを移動する
- IO::SEEK_END : offsetをファイルの末尾からの相対位置としてファイルポインタを移動する

#### ファイルの先頭に戻す
- *io*.**rewind**

#### ファイルの長さを指定したサイズに切り詰める
- *io*.**truncate**(*size*)

## バイナリモード、テキストモード

ファイルに含まれる改行文字を入力元、出力先に合わせて変換する → **テキスト**モード
変換しない → **バイナリ**モード

#### バイナリモードに変換する
- *io*.**binmode**

## バッファリング
> writeメソッドやprintメソッドでIOオブジェクトに対する操作を行うと、プログラムの内部にいったんコピーを作成します。このような領域のことをバッファといいます。 ~ 中略 ~
バッファを使ってデータを処理することをバッファリングといいます
| たのしいRuby P353

2つのコンソール出力のうち標準エラー出力はバッファリングをおこわないため、出力順がコードの順と異なる場合がある

#### 出力バッファにたまっているデータを強制的に出力
- *io*.**flush**

#### バッファへの書き込みのたびにflushするかどうか
- *io*.**sync**

- *io*.**sync=**(*state*) 
*io*.**sync** = true でバッファの書き込みのたびにflushが呼ばれるようになる

## コマンドとのやりとり

- **IO**.**popen**(*command*, *mode*)

```rb
pattern = Regexp.new(ARGV[0])
filename = ARGV[1]

if /.gz$/ =~ filename
  file = IO.popen("gunzip -c #{filename}")
else
  file = File.open(filename)
end

file.each_line do |text|
  if pattern =~ text
    print text
  end
end
```

**open(|***command*, *mode*)でも同じことができる

## open-uri

HTTPやFTPのURLを普通のファイルのように開くことができる

```rb
require 'open-uri'

open("http://www.homes.co.jp") do | io |
  puts io.read
end
=>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional
.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<title>不動産・賃貸・住宅情報（マンション・一戸建て）ならHOME'S【ホームズ】</title>
===
====
```

## stringio

IOオブジェクトのモック

```rb
require 'stringio'

io = StringIO.new
io.puts("A")
io.puts("B")
io.puts("C")
io.rewind
p io.read
=>
"A\nB\nC\n"
```