ファイルに対する入出力を行いたい場合など、高度な入出力操作が必要な場合は、標準ライブラリに含まれるIOクラスが役に立ちます。操作対象がファイルであっても標準入出力であっても、統一されたインターフェースを通じてデータを操作できます。
後述しますが、FileクラスはIOクラスを継承しています。まずは、Fileクラスの使い方について解説します。
ファイルを読み込んで処理する基本的な例を、io04.rbに示します。
file = File.open("wonderland.txt", "r") file.each do |line| puts "** #{line.chomp} **" end file.close
$ ruby io04.rb ** Alice ** ** White Rabbit ** ** Cheshire Cat ** ** March Hare **
1行目でFileクラスのインスタンスを生成しています。FileクラスはIOクラスを継承しているクラスなので、入出力をつかさどるIOクラスを、ファイルに対する入出力に特化させたものです。
「open」メソッドの第1引数にはファイル名を、第2引数には、ファイルの読み書きモードを指定します。
ここでは、読み込むだけでよいので読み取り専用を表す「"r"」を指定しています。ファイルに書き込む場合には「"w"」を指定します。モードの詳細については、Ruby公式の「module function Kernel.#open」が参考になります。
参照先のページにあるように、Kernelモジュールにも「open」というメソッドが定義されているので、io04.rbの1行目は「file = open("wonderland.txt", "r")」とも書けます。こちらの方がスッキリしていますが、ここではIOクラスやFileクラスを利用していることを意識するために、あえてKernelモジュールの「open」を使わずに説明しています。
io04.rbの3〜5行目では、「each」メソッドを利用してwonderland.txtを1行ずつ変数lineに読み取り、io03.rbのようにアスタリスクを付加して標準出力に流しています。Fileオブジェクトを使う場合でも、取り出してきた1行には改行が含まれているので「line.chomp」としていることに注意してください。
7行目では「close」メソッドを使ってファイルを閉じています。
ファイルは開いたら必ず閉じることを忘れないでください。もし閉じ忘れてもオペレーティングシステムがよしなにしてくれる場合がほとんどですが、ファイルは必ず閉じるべきです。それでもプログラマーは人間ですから、ファイルを閉じ忘れることもあります。しかし「open」メソッドはブロックを引数として受け取ってくれるので、io04.rbと同等の処理をio05.rbのように記述することもできます。
File.open("wonderland.txt", "r") do |file| file.each do |line| puts "** #{line.chomp} **" end end
$ ruby io05.rb ** Alice ** ** White Rabbit ** ** Cheshire Cat ** ** March Hare **
「open」メソッドのブロック引数にはFileオブジェクトが格納されるので、ブロック内でファイルの読み書きを行えます。io05.rbの2〜4行目では、io04.rbと同様にファイルから1行ずつ読み込んで標準出力に流しています。そしてブロックの終端に届いた時点で開いたファイルは自動的に閉じられるので、プログラマーは明示的に「close」メソッドを呼ぶなどの作業をしなくてもよくなります。
ここまで、ファイルを読み込む例を見てきました。もちろん、Fileオブジェクトを通して文字を書き込むこともできます。io06.rbにファイルに書き込む例を示しましょう。
File.open("teapot.txt", "w") do |file| file.print("darjeeling tea\n") file.puts("assam tea") file.printf("%s\n", "ceylon tea") end
$ ruby io06.rb $ cat teapot.txt darjeeling tea assam tea ceylon tea
1行目で、ファイルを読み込む場合と同様、「open」メソッドを使ってファイルを開いています。ただし、今回はファイルに書き込みを行うため、モードに「"w"」を指定しています。
変数fileにはFileオブジェクトが格納されるので、後は「print」「puts」「printf」といったおなじみのメソッドでファイルに書き込めます。2〜4行目が実際にファイルに書き込んでいる部分です。
ここまでの説明で、Fileクラスはファイル操作に特化した入出力クラスであることを確認しました。ファイルに限らず、何らかの入出力が可能なものを表すクラスがIOクラスです。
FileクラスはIOクラスを継承しています。このような関係があるために、相手がファイルであっても一般的な入力や出力であっても、同じようなインターフェースを通してデータのやりとりができます。
最も身近なIOオブジェクトといえば、定数として定義されている標準出力を表す「STDOUT」や標準入力を表す「STDIN」でしょう。pryを使ってそれらのIOオブジェクトを確認してみましょう。
[1] pry(main)> STDOUT => #<IO:<STDOUT>> [2] pry(main)> STDIN => #<IO:<STDIN>> [3] pry(main)> $stdout => #<IO:<STDOUT>> [4] pry(main)> $stdin => #<IO:<STDIN>>
[1]や[2]でIOオブジェクトが得られていることを確認してください。また、[3]と[4]のように、グローバル変数「$stdout」「$stdin」を通して標準出力や標準入力のIOオブジェクトを取得することもできます。
これらのIOオブジェクトを活用すれば、IOクラスに定義された豊富なメソッドを利用できます。io07.rbに標準入力から入力された文字列を1行ずつ読み、行番号を付与して標準出力に流す例を示しましょう。
STDIN.each.with_index(1) do |line, i| puts "#{i}: #{line.chomp}" end
$ ruby io07.rb < wonderland.txt 1: Alice 2: White Rabbit 3: Cheshire Cat 4: March Hare
「STDIN」は標準入力を表すIOオブジェクトなので、「each」メソッドを用いてEnumeratorオブジェクトを得ることができます。Enumeratorオブジェクトでは「with_index」メソッドが使え、ブロック変数「line」に読み込んだ行の内容、「i」にインデックスが入ります。このとき、「with_index」メソッドの引数に1を与えているので、初期値が1になり、io07.rbの実行例のような出力を得ることができます。
「STDIN」「STDOUT」が真価を発揮するのは、他のコマンドとパイプでつないで利用するようなケースです。例えば、以下のように実行することで、「ls」コマンドの実行結果に行番号を付与し、さらにリダイレクトによってls_with_linenum.txtにその結果を出力できます。
$ ls -al | ruby io07.rb > ls_with_linenum.txt $ cat ls_with_linenum.txt 1: total 56 2: drwxr-xr-x 14 flost staff 476 Nov 21 14:18 . 3: drwxr-xr-x 15 flost staff 510 Nov 21 09:04 .. 4: -rw-r--r-- 1 flost staff 45 Nov 21 11:16 io01.rb 5: -rw-r--r-- 1 flost staff 51 Nov 21 11:23 io02.rb 6: -rw-r--r-- 1 flost staff 51 Nov 21 11:43 io03.rb 7: -rw-r--r-- 1 flost staff 106 Nov 21 13:09 io04.rb 8: -rw-r--r-- 1 flost staff 106 Nov 21 13:27 io05.rb 9: -rw-r--r-- 1 flost staff 137 Nov 21 13:40 io06.rb 10: -rw-r--r-- 1 flost staff 71 Nov 21 14:08 io07.rb 11: -rw-r--r-- 1 flost staff 0 Nov 21 14:19 ls_with_linenum.txt 12: -rw-r--r-- 1 flost staff 5 Nov 21 10:32 rabbit.txt 13: -rw-r--r-- 1 flost staff 14998 Nov 21 14:18 ruby_21_guide_10.md 14: -rw-r--r-- 1 flost staff 36 Nov 21 13:40 teapot.txt 15: -rw-r--r-- 1 flost staff 43 Nov 21 11:26 wonderland.txt
原稿執筆時の筆者の作業ディレクトリに含まれているファイルの一覧が、行番号とともにls_with_linenum.txtに格納されています。
このように、標準入力から受け取ったデータを加工して標準出力に流すようなプログラムを、一般的に「フィルター」と呼びます。
UNIX系OSでは豊富なフィルターが用意されています。実は、io07.rbのようなプログラムを使わずとも、「nl」というコマンドを使えば行番号を付与できます。他にも文字コードを変更するための「nkf」や、ソートに使える「sort」、重複する行を取り除く「uniq」など、いろいろなフィルターを利用できます。
練習課題として、「sort」コマンドや「uniq」コマンドをRubyで再実装してみるのも面白いでしょう。
もし標準のコマンドでは実現できないようなテキスト処理があっても、「STDIN」「STDOUT」を活用することでRubyを使ったオリジナルのフィルターを簡単に作ることができます。ぜひ、日々の業務の効率化に活用してください。
今回は、Rubyから入出力を行う方法を学びましたが、いかがでしたでしょうか。次回は、スレッド、ファイバー、プロセスといったトピックについて解説します。UNIX系OSに慣れた方はお分かりだと思いますが、特にプロセスについては、入出力とも密接に関連しています。
麻田 優真(Rails技術者認定シルバー試験問題作成者)
イタリア、ローマ生まれ。中学生のころHSPに初めて触れ、プログラミングの楽しさを知る。オープンソースやハッカーカルチャーを好む。C#からRubyに転向したときに、動的型付け言語の柔軟性やメタプログラミングの魅力に感動し、Rubyとともにプログラマーとしての人生を歩む決意を固める。
現在は奈良先端科学技術大学院大学で学生として所属するかたわら、株式会社アジャイルウェアでプログラマーとして従事。Ruby on Railsによるコンシューマー向けのWebサービスの開発などに尽力している。
好きなメソッドは、define_method。
Twitter:@Mozamimy、ブログ:http://blog.quellencode.org/
山根 剛司(Ruby業務開発歴7年)
兵庫県生まれ。1997年からベンチャー系のパッケージベンダーで10年間勤務。当時、使用していた言語はJavaとサーバーサイドJavaScript。
2007年よりITコンサル会社に転職し、Rubyと出会って衝撃を受ける。基幹システムをRuby on Railsで置き換えるプロジェクトに従事。それ以来Ruby一筋で、Ruby on Railsのプラグインやgemも開発。
2013年より、株式会社アジャイルウェアに所属。アジャイルな手法で、Ruby on Railsを使って企業向けシステムを構築する業務に従事。
Ruby関西所属。
Twitter:@spring_kuma、Facebook:山根 剛司
Copyright © ITmedia, Inc. All Rights Reserved.