もくじ
したいこと
任意のファイルから中身を取り出したい。※今回の記事ではファイルという言葉の中にディレクトリは含みません。
やり方
結論からいうと、
file = File.open("hoge") content = file.read
これで変数content
の中にhoge
というファイルの中身がそのまま文字列として格納されます。
以下で詳しく説明します。
Fileクラス
ファイルに対してなにかしたい場合はFileクラスを使えばおkです。とてもわかりやすい名称で僕のような初心者にも親切です。
Fileクラスに関してリファレンスマニュアルではこう説明されています。
ファイルアクセスのためのクラスです。 通常 Kernel.#open または File.open を使って生成します。 IO クラスがインクルードしている File::Constants は File クラスに関係する定数を格納したモジュールです。また File::Stat は stat 構造体( stat(2) 参照)を表すクラスです。
Rubyのコード内で何かを扱おうとするときは何かしらのオブジェクトにする必要があり、ファイルはFileクラスのオブジェクトとして扱うことになります。
そのオブジェクトはリファレンスマニュアルにあるように、File.open
のようにopen
メソッドを使用して生成するのが一般的なようです。
・・・。
なんか違和感あるなと思いませんか?
僕はリファレンスマニュアルのこの説明を読んで、実際にirb
で試してみたりして動くことは確認したのですが、ややしばらくの間なんとなくこの使い方に違和感がありました。
で、あるとき思いました。
「File.new
じゃないんかい。」と。
そう。他のクラスのオブジェクトを生成する際は毎回new
メソッドを使っています。
なのに今回はopen
なんかい。
そう思って確認したら、なんのことはない、new
メソッドもちゃんとありました。
なんならopen
とまとめて記載されています。
要するに、まぁ似たような(同じ?)もんってことです。(厳密に言うと違うのかもしれませんが、僕レベルでは気にする必要がない程度なんじゃないかと思います。)
書き方もほぼ同じです。
new(path, mode = "r", perm = 0666) -> File open(path, mode = "r", perm = 0666) -> File open(path, mode = "r", perm = 0666) {|file| ... } -> object
path で指定されるファイルをオープンし、File オブジェクトを生成して返します。 path が整数の場合はファイルディスクリプタとして扱い、それに対応する File オブジェクトを生成して返します。IO.open と同じです。ブロックを指定して呼び出した場合は、File オブジェクトを引数としてブロックを実行します。ブロックの実行が終了すると、ファイルは自動的にクローズされます。ブロックの実行結果を返します。
じゃあなんでわざわざopen
メソッドが定義されていて、それを使うのが一般的なんでしょう。
正直、ちゃんとした理由は知りません。
が、個人的にはそっちのほうが直感的にわかりやすいからっていうのが大きいんじゃないかと思ってます。
頭の中で日本語で考えるときも
「ファイルオブジェクトを新しく生成する(File.new
)」よりも「ファイルを開く(File.open
)」の方がファイルをいじくってる感が出てませんか?(個人の感想です)
というかnew
ってそもそも動詞として訳しにくい。
まぁとにかく、あとはopen
の引数に自分が開きたいファイルの名前を渡してあげればおkです。
IOクラス
話が脇道にそれてしまった気もしますが、たぶん大丈夫です。
リファレンスマニュアルにおけるnew
とopen
の説明の中にこんな記載がありました。
IO.open と同じです。
IOってなんじゃい。
この感じだとクラスですね。
リファレンスマニュアル召喚。
基本的な入出力機能のためのクラスです。
以上。ほーん。 InputOutputでIOね。
Fileクラスの話をしていたはずなのに、なんでIOクラスの話になるかと言うと、IOクラスがFileクラスのスーパークラスだからです。言い換えると、FileクラスはIOクラスを継承しているクラスになります。
IOクラスのInputとOutputというのは主語が 「Rubyで書かれたプログラムの」になるわけで、あるプログラムに対するあらゆるInとOutを司るクラスということだと思います。
なので、その「あらゆるInとOut」の中にはプログラムの外部にあるファイルの情報をとってきたり、なんらかの結果を外部ファイルとして出力したりということも当然含まれるため、こういう継承関係になっているのだと思います。
ここでもう一度、この記事の冒頭で書いたコードを見てみます。
file = File.open("hoge") content = file.read
一行目はFileクラスの説明で書いたように、open
メソッドを使ってFileオブジェクトを生成しています。
そして二行目でやることはread
というメソッドを使ってFileオブジェクトから中身を取得することなのですが、実はこのread
メソッドはFileクラスのメソッドではなくIOクラスのメソッドです。
readメソッド
read
メソッドの説明も見てみます。
read(length = nil, outbuf = "") -> String | nil
length バイト読み込んで、その文字列を返します。 引数 length が指定された場合はバイナリ読み込みメソッド、そうでない場合はテキスト読み込みメソッドとして動作します。既に EOF に達していれば nil を返します。ただし、length に nil か 0 が指定されている場合は、空文字列 "" を返します。例えば、open(空ファイル) {|f| f.read } は "" となります。
このメソッドを使って取得したファイルの中身というのは改行文字等も含めてまるごと一行の文字列になります。 そのため、そこから更に何らかの情報を得たい場合は、行数を調べるために改行文字を数える、単語数を調べるために区切り文字を指定して切り分ける、といったようにひと手間加える必要があります。
IOクラスにはこの他にもファイルの中身をぶっこ抜いてくる系のメソッドがいくつもあって、例えばreadlines
というメソッドはread
と同じくファイルの中身を全て読み込んで返してくれますが、read
が全てまるっと何の下ごしらえもしていない一行の文字列として返してくれるのに対し、readlines
は各行を要素として持つ配列を返してくれます。つまり、こちらが改行文字を数えたりしなくても、ちゃんと行を認識して切り分けてお皿に盛ってくれるわけです。(優しい)
この辺に関しては優劣ではなく仕様なので、自分が書きたい処理に適したメソッドを使えば幸せになれると思います。
まとめ
こんな短いコードのために長々と書いてしまいましたが、何はともあれこれで無事にファイルの中身をぶっこ抜いてくることができました。
あとは煮るなり焼くなりお好きにどうぞ。
記載内容に間違い等があった場合にはこっそり教えていただければ幸いです。 最後までお読みいただきありがとうございました。