Rubyの入出力(IO)完全ガイド:ファイルディスクリプタから標準ストリームまで徹底解説
「I/O」とは「Input/Output(入出力)」の略称です。
入力とは、コンピュータ、Rubyメソッド、さらには人間の脳といった対象に取り込まれる、あらゆるデータや情報のことを指します。
入力の例
- キーボードで押下するキー
- マウスのクリック
- 読んでいる本
一方、出力とは入力の結果として外に出てくるものすべてです。
出力の例
- 1 + 1 の計算結果
- 読んだ記事の要約文
- 淹れたコーヒー
Rubyにおいて「I/O」と言う場合、通常はファイルの読み書き、ネットワークソケットの操作、画面への情報表示を指します。
IOクラスを理解する
Rubyでは IO もまたクラスの一つであり、File や Socket など他のオブジェクトの親クラスにあたります。
すべてのIOオブジェクトは以下の操作を備えています:
- open / close(開く・閉じる)
- read(読み込む)
- write(書き込む)
IOオブジェクトはファイルディスクリプタを基盤としています。
コンピュータ上で動作するすべてのプロセスは内部に情報テーブルを持ち、その一部にはファイルディスクリプタのリストが含まれています。
ファイルディスクリプタとは、整数とファイル名を対応付けるマッピングのことです。
Linuxでは、このファイルディスクリプタの一覧を次のコマンドで確認できます。
# 別のウィンドウでirbが開いているものとします ls -lh /proc/`pgrep irb`/fd/ # lrwx------ 1 jesus jesus 64 Feb 3 15:54 0 -> /dev/pts/3 # lrwx------ 1 jesus jesus 64 Feb 3 15:54 1 -> /dev/pts/3 # lrwx------ 1 jesus jesus 64 Feb 3 15:54 2 -> /dev/pts/3
Rubyでは sysopen メソッドを使って新しいファイルディスクリプタを作成できます。
例:
fd = IO.sysopen('/dev/null', 'w')
# 10
これで、このファイルディスクリプタ(10)を使って通信チャネルを開けるようになります。
次のように記述します:
io = IO.new(fd)
そして書き込みも可能です:
io << 'orange'
注目すべきは、IO がファイルディスクリプタの先が実際のファイルなのか、デバイス(ハードディスクなど)なのか、ネットワークソケットなのかを一切気にしないという点です。
書き込み用のファイルディスクリプタを開く方法
sysopen で新しいファイルディスクリプタを作成する場合も、File.open でファイルを開く場合も、いくつかのモードオプションを選択できます。
デフォルトは読み取り専用モードです。
モードは次のように指定できます:
r(読み取り:デフォルト)w(書き込み)w+(読み書き両方)a(追記)
例:
fd = IO.sysopen('/dev/tty1', 'w+')
このコードは /dev/tty1 を読み書き両用モードで開きます。
3つの標準IOストリーム
新しいプログラム(エディタ、ターミナル、Rubyインタプリタなど)を起動すると、デフォルトで3つの通信チャネル、すなわち3つのファイルディスクリプタが開かれます。
それらは:
- stdin(標準入力)
- stdout(標準出力)
- stderr(標準エラー)
Rubyでは、これらに定数形式またはグローバル変数形式で直接アクセスできます。
例:
$stdout << "abc\n"
$stdout への書き込みは、puts メソッドの呼び出しと同じ結果になります。
標準出力はターミナルに関連付けられています。
lrwx------ 1 jesus jesus 64 Feb 3 15:54 0 -> /dev/pts/3
この /dev/pts/3 は仮想デバイスであり、ターミナルがここからテキストを読み取って画面に表示しています。
標準エラーも出力に使われますが、通常のテキストとは異なる形——たとえば大きなフォントや別の色——で内容を表示できるようになっています。標準エラーへの出力には warn メソッドを使用します。
標準入力はユーザー入力を読み取る場所です。標準入力からの読み取りには gets メソッドを使用します。
STDOUTのフラッシュとバッファリングの無効化
IOオブジェクトには内部バッファがあり、一定量の入力データが読み出し可能になる前に保持される仕組みになっています。
だからこそ、puts を呼び出したのに、プログラムが終了するかバッファが満杯になるまで何も表示されない、という現象が起こるのです。
バッファリングは次のように無効化できます:
$stdout.sync = true
または、手動でバッファをフラッシュすることもできます:
$stdout.flush
バッファを無効化すれば、待ち時間なく即座に出力を確認できるようになります。
チャンク単位での読み込み
IOオブジェクトからの読み込みは、EOF(END-OF-FILE)シグナルでストリームが終了するか、ストリームが閉じられるか、指定したバイト数を受信するまで read を続けます。
ネットワークプログラミングや巨大なファイルを扱う場合は、小さなチャンク単位で読み込むのが有効です。
チャンク読み込みは次のように実装できます:
data = "" until io.eof? data << io.read(2048) end
このコードなら、ダウンロード全体が完了するのを待たずに、取得できたデータから順次処理できます。
注意点として、eof? は「ブロック」します。ブロックとは、OSが読み取るべき新規データがあると通知してくるまで、コードがスリープ状態になることを意味します。
IOパイプで双方向通信を行う方法
Rubyではパイプを作成できます。パイプとは、互いに接続された一対のファイルディスクリプタのことで、プロセス間通信(IPC:Inter-Process Communication)に利用されます。
例:
read, write = IO.pipe
これを fork メソッド(Windowsでは利用不可)と組み合わせると、現在のプロセスのコピーを作成でき、2つのプロセスがパイプ経由で通信できるようになります。
例:
if fork
write.close
puts "Message received: #{read.gets}"
read.close
else
read.close
write << "Buy some bananas!"
write.close
end
実行結果:
# Message received: Buy some bananas!
Kernel#openメソッドの使い方と注意点
Rubyには非常に柔軟な open メソッドがあります。ファイルを開くだけでなく、URLからデータを読み取ったり、外部コマンドを実行するためのパイプを開くことさえ可能です。
例:
open "abc.txt"
だからこそ、このメソッドは使用を避けるべきとされています。何でもできてしまうことが、逆にセキュリティリスクとなるからです。
代わりに、目的に合った専用の open メソッドを使うのがベストプラクティスです。
例:
File.open # ファイルを開く IO.popen # プロセスを開く URI.parse(...).open # URLを開く
これらはすべて IO オブジェクトを返します。
まとめ
本記事では、Rubyにおける入出力について学びました。ファイルディスクリプタの仕組み、IO クラスの基本操作、バッファリングの制御、チャンク読み込み、標準IOストリーム、そしてパイプによるプロセス間通信まで理解できたはずです。
この記事が役に立ったら、ぜひ周りの方にもシェアしてください。
最後までお読みいただきありがとうございました。
-
Rubyで実践する関数型プログラミング完全ガイド ― 純粋関数・イミュータブルデータ・カリー化の基本
Rubyを書いていると、「関数型プログラミング」という言葉を目にする機会が増えてきます。しかし、実際にどんなものなのか、自分のコードに取り入れるべきなのか、疑問に感じている方も多いのではないでしょうか。 関数型プログラミングとは、具体的に何を指すのか? オブジェクト指向プログラミング(OOP)とは何が違うのか? Rubyでも関数型的な書き方を採用すべきなのか? この記事では、これらの疑問にわかりやすく答えながら、Rubyで今日から使える関数型プログラミングの考え方とテクニックを解説します。 関数型プログラミングとは? 関数型プログラミングは一時的な流行や難しい専門用語ではなく、長い歴史を持
-
RubyのEnumerableモジュール入門ガイド〜筆者イチオシのeach_consメソッドを徹底解説〜
Enumerableモジュールとは? Enumerableは繰り返し処理(イテレーション)用メソッドのコレクションで構成されるRubyのモジュールであり、Rubyが多くの開発者に愛される大きな理由のひとつとなっています。 Enumerableには、次のような便利なメソッドが含まれています: map select inject Enumerableのメソッドは、ブロックを渡すことで動作します。 ブロックの中には、各要素に対して実行したい処理を記述します。 例えば: [1,2,3].map { |n| n * 2 } このコードを実行すると、すべての数値が2倍になった新しい配列が得られます