PHP でファイルを読み取るための正しい方法

どういう場合に fopen、fclose、feof、fgets、fgetss、そして fscanf を使うべきかを学ぶ

PHP のさまざまなファイル関数の使い方を学びましょう。まず fopen や fclose、feof などの基本的なファイル関数について調べ、さらに fgets や fgetss、fscanf などの読み取り関数について学びます。そして、1 行か 2 行のコードで全ファイルを処理できる関数についても見て行きます。

2013年 5月 17日 ― 読者からのコメントおよび著者からのリクエストに従って、リスト 3 の 3 行目の等号 (=) を削除するようにコードを更新しました。

Roger McCoy, IT Specialist, Freelance Writer and Consultant

Roger McCoy は開発者として、C や Java、JavaScript、Perl、PHP、Visual Basic など、多くのプログラミング言語を扱ってきました。彼は PHP アプリケーションの開発に 5 年の経験がありますが、おそらく彼は、コールセンター業界の技術者としての仕事で最も知られているかもしれません。



2013年 6月 13日 (初版 2007年 2月 13日)

さまざまな方法を探る

PHP のような最近のプログラミング言語を扱う際の楽しみとして、非常に多くのオプションがあることがあげられます。PHP は、特にファイル処理に関して、Perl の掲げる標語「同じことをする方法は複数ある」を簡単に奪ってしまいそうです。しかし、これほど多くのオプションがあると、ある作業のためのベスト・ツールは何なのでしょう。当然ながら、それに対する本当の答えは、ファイルを解析する際のゴールによって異なります。そのため、すべてのオプションを調べる価値があるのです。


従来の fopen メソッド

fopen メソッドは、昔からの C/C++ プログラマーにとって、おそらく最もなじみ深いものでしょう。その理由は、これらの言語で作業する人が長年の間、常用ツールとして fopen メソッドを使ってきたからです。どのメソッドを使う場合も、fopen (データを読むための関数) を使ってファイルを開き、そして fclose を使ってファイルを閉じるという標準的なプロセスがあります (リスト 1)。

リスト 1. ファイルを開き、fgets でファイルを読み取る
$file_handle = fopen("myfile", "r");
while (!feof($file_handle)) {
$line = fgets($file_handle);
echo $line;
}
fclose($file_handle);

こうした関数は、経験の長いプログラマーの大部分にとってはおなじみのものですが、ここではこれらの関数を分析してみることにします。実質的には、次のステップを実行しています。

  1. ファイルを開きます。$file_handle は、そのファイル自体への参照を保存します。
  2. 既にファイルの最後に達したかどうかをチェックします。
  3. ファイルの最後に達するまでファイルを読み続け、各行を、読み取りながら出力します。
  4. ファイルを閉じます。

こうしたことを念頭に置いて、ここで使用されている各関数を調べてみましょう。

fopen

fopen 関数はファイルへの接続を作成します。「ファイルへの接続を作成する」と言う理由は、fopen はファイルを開くことの他に、次のように URL を開くこともできるからです。

$fh = fopen("http://127.0.0.1/", "r");

このコードは上記のページへの接続を作成します。これにより、そのページがまるでローカル・ファイルであるかのように読めるようになります。

注意: fopen に使われている「r」は、そのファイルを読み取り専用として開いていることを示します。ファイルに対する書き込みはこの記事の対象外なので、ここでは「r」以外のオプションについては取り上げません。ただし、クロス・プラットフォームの互換性を維持するためにバイナリー・ファイルから読み取る場合には、「r」を「rb」に変更する必要があります。これについての例を後ほど示します。

feof

feof コマンドは、既にファイルの最後まで読み終わったかどうかを検出し、True あるいは False を返します。リスト 1 のループは、ファイル「myfile」の最後に達するまで継続されます。ここで、URL を読んでいてソケットがタイムアウトした場合にも、(読むべきデータがなくなるため) feof が False を返すことに注意してください。

fclose

少しとばしてリスト 1 の最後を見ると、fclose が fopen と逆の関数として動作しています。つまり fclose は、ファイルあるいは URL への接続を閉じています。この関数の後では、もはやファイルあるいはソケットから読み取ることはできません。

fgets

リスト 1 の最後から数行戻ると、ファイル処理の核心があり、実際のファイルの読み取りが行われています。この fgets 関数こそ、この最初の例のえり抜きの手段なのです。この関数は、ファイルから 1 行のデータを取得し、それを文字列として返します。そうすると、そのデータを出力したり処理したりできるようになります。リスト 1 の例は、ファイル全体を見事に出力します。

処理対象とするデータ・チャンクのサイズを制限する場合は、fgets に引数を追加して行の最大長を制限することができます。例えば下記のコードを使うと、1 行を 80 文字に制限することができます。

$string = fgets($file_handle, 81);

C での「\0」文字列終了記号にならって、実際に必要な行の長さよりも 1 大きい数に設定します。つまり、上記の例では 80 文字にしたいので、81 を使っています。この関数の行制限を使う際には、この余分な 1 文字を追加するのを忘れないように習慣づけてください。

fread

fgets 関数は、数多くあるファイル読み取り関数の 1 つにすぎません。fgets は他の関数よりも一般的に使われますが、それは行ごとに解析した方が意味のあることが多いためです。実際には、他のいくつかの関数も似たような機能を提供しています。しかし、行ごとの解析が常に要求されるとは限りません。

そこに fread が登場します。fread 関数は fgets とは少し目的が異なり、バイナリー・ファイル (つまり基本的に人間が読み取れるテキストから構成されていないファイル) から読み取ることを目的としています。バイナリー・ファイルには「行」の概念はないため (論理データによる構成体は、通常は改行で終わりません)、読み取りたいバイト数を必ず指定する必要があります。

$fh = fopen("myfile", "rb");
$data = fread($file_handle, 4096);

バイナリー・データを扱う

この関数の例では、fopen の場合とは少し異なる引数が使われていることに注意してください。バイナリー・データを扱う場合には、fopen に必ず b オプションを含めることを忘れないでください。それを忘れてしまうと、Microsoft® Windows® システムはそのファイルを正しく処理できないかもしれません。これは Microsoft® Windows® システムでは改行の扱いが異なるためです。この問題は、Linux® システム (あるいは UNIX® に似た他のシステム ) で作業する場合には無関係に思えるかもしれませんが、たとえ Windows 用の開発ではない場合であっても、こうした方がクロス・プラットフォームの維持管理には有効であり、何よりも、これは従うべき適切なプラクティスなのです。

上記は、4,096 バイト (4 KB) のデータを読み取ります。どのような数字を指定しても、fread は 8,192 バイト (8 KB) を越えるデータを読み取らないことに注意してください。

ファイルが 8 KB 以下であるとすると、次のコードはファイル全体を 1 つの文字列に読み込みます。

$fh = fopen("myfile", "rb");
$data = fread($fh, filesize("myfile"));
fclose($fh);

もしファイルがこれよりも長い場合には、残り部分をループを使って読み込む必要があります。

fscanf

文字列の処理に戻ると、fscanf も従来の C ファイル・ライブラリー関数に従います。fscanf になじみがない人のために説明すると、fscanf はファイルからフィールド・データを変数の中に読み込みます。

list ($field1, $field2, $field3) = fscanf($fh, "%s %s %s");

この関数に使われる書式制御文字列については PHP.net など多くの場所で説明されているため、ここでは省略します。ここでは、文字列の書式制御は非常に柔軟だと言っておけば十分でしょう。重要な点として、すべてのフィールドが関数の戻り値に含まれていることに注意してください。(C では引数として渡されます。)

fgetss

fgetss 関数は従来のファイル関数からは独立しており、PHP の強力さをよく示してくれます。この関数は fgets のように動作しますが、HTML や PHP のタグを見つけると、それらを全部取り去って、生のテキストのみを残します。例えば、下記の HTML ファイルを見てください。

リスト 2. HTML ファイルの例
<html>
<head><title>My title</title></head>
<body>
<p>If you understand what "Cause there ain't no one for to give you no pain"
means then you listen to too much of the band America</p>
</body>
</html>

次に、このファイルを fgetss 関数でフィルターします。

リスト 3. fgetss を使う
$file_handle = fopen("myfile", "r");
while (!feof($file_handle)) {
   echo fgetss($file_handle);
}
fclose($file_handle);

そうすると、下記が出力されます。

My title

If you understand what "Cause there ain't no one for to give you no pain"
    means then you listen to too much of the band America

fpassthru 関数

どのようにファイルを読み取ってきた場合であっても、fpassthru を使うと、残りのデータを標準の出力チャネルにダンプすることができます。

fpassthru($fh);

この関数もデータを出力します。そのため、変数の中のデータを取得する必要はありません。


非線形ファイル処理: ファイル内をジャンプする

当然のことですが、上記のどの関数を使った場合も、あるファイルを一定の順序で読むことしかできません。もっと複雑なファイルの場合には、ファイルのあちこちを行ったり来たりする必要があります。こうした場合に fseek が便利です。

fseek($fh, 0);

上記の例は、ジャンプしてファイルの先頭に戻ります。それほど大きく逆戻りしたくなければ、例えば 1 キロバイト戻りたい場合は、単純に次のように書きます。

fseek($fh, 1024);

PHP V4.0 以降では、他にもいくつかのオプションがあります。例えば、現在の位置から 100 バイト先にジャンプしたい場合には、下記を使うことができます。

fseek($fh, 100, SEEK_CUR);

同様に、100 バイト戻りたい場合には下記を使います。

fseek($fh, -100, SEEK_CUR);

ファイルの最後から 100 バイト前に戻りたい場合には、SEEK_CUR ではなく SEEK_END を使います。

fseek($fh, -100, SEEK_END);

新しい位置に達したら、fgets や fscanf、あるいは他の任意の関数を使ってデータを読むことができます。

注意: URL を参照するファイル・ハンドルに対して fseek を使うことはできません。


ファイル全体を取得する

PHP は、さらにユニークで強力な方法でファイル処理を行うことができます。PHP では、巨大なデータ・チャンクを 1 行か 2 行で処理できるのです。例えば、あるファイルを取得し、その内容全体を Web ページに表示するにはどうしたらよいのでしょう。先ほど、fgets でループを使った例を見ました。しかしこれを、もっと単純にできないのでしょうか。このプロセスは、ファイル全体を 1 つの文字列の中に収める fgetcontents を使えば、信じられないほど簡単なのです。

$my_file = file_get_contents("myfilename");
echo $my_file;

ベスト・プラクティスではありませんが、このコマンドをもっと簡潔に次のように書くことができます。

echo file_get_contents("myfilename");

この記事は基本的にローカル・ファイルの処理について説明していますが、こうした機能を使うと、他の Web ページを取得してエコーし、解析できることは注目に値します。

echo file_get_contents("http://127.0.0.1/");

このコマンドは、実質的に次と同じです。

$fh = fopen("http://127.0.0.1/", "r");
fpassthru($fh);

皆さんはこれを見て、「これでも手間がかかりすぎる」と思っているに違いありません。PHP 開発者も皆さんと同意見でしょう。そこで、上記のコマンドを次のように短くします。

readfile("http://127.0.0.1/");

readfile 関数は、あるファイルまたは Web ページの内容全体を、デフォルトの出力バッファーにダンプします。このコマンドはデフォルトで、失敗するとエラー・メッセージを出力します。この動作を避けるためには (避けたい場合には)、次のようにします。

@readfile("http://127.0.0.1/");

もちろん、実際にファイルを解析したい場合には、file_get_contents が返す 1 つの文字列では大きすぎるかもしれません。そのため皆さんは、まずは split() 関数を使って少し分割しようと思うかもしれません。

$array = split("\n", file_get_contents("myfile"));

しかし、正にそれを完璧に行ってくれる関数があるのに、なぜそんな手間をかける必要があるのでしょう。PHP の file() 関数はこれを 1 ステップで行い、行に分割した文字列の配列を返すのです。

$array = file("myfile");

上の 2 つの例の間に少し違いがあることに注意してください。split コマンドは改行を除去しますが、file コマンドを使う場合には (fgets コマンドの場合と同じように)、改行は配列の中の文字列に付加されたままです。

ただし PHP の強力さは、そんな違いをはるかに超えています。parse_ini_file を使うと、PHP 形式の .ini ファイル全体を、1 つのコマンドで解析できるのです。parse_ini_file コマンドは、リスト 4 のようなファイルを受け付けます。

リスト 4. .ini ファイルの例
; Comment
[personal information]
name = "King Arthur"
quest = To seek the holy grail
favorite color = Blue

[more stuff]
Samuel Clemens = Mark Twain
Caryn Johnson = Whoopi Goldberg

次のコマンドは、このファイルを配列の中にダンプし、そしてその配列を出力します。

$file_array = parse_ini_file("holy_grail.ini");
print_r $file_array;

下記はそれを出力した結果です。

リスト 5. 出力
Array
(
[name] => King Arthur
[quest] => To seek the Holy Grail
[favorite color] => Blue
[Samuel Clemens] => Mark Twain
[Caryn Johnson] => Whoopi Goldberg
)

もちろん皆さんは、このコマンドが部分同士をつなげてしまっていることに気が付くと思います。これがデフォルト動作ですが、この動作は 2 番目の引数をブール変数の parse_ini_file: process_sections に渡すことで、容易に修正することができます。そこで process_sections を True に設定します。

$file_array = parse_ini_file("holy_grail.ini", true);
print_r $file_array;

そうすると、次のような出力が得られます。

リスト 6. 出力
Array
(
[personal information] => Array
(
[name] => King Arthur
[quest] => To seek the Holy Grail
[favorite color] => Blue
)

[more stuff] => Array
(
[Samuel Clemens] => Mark Twain
[Caryn Johnson] => Whoopi Goldberg
)

)

PHP はデータを、容易に解析可能な多次元配列に置いたのです。

これは PHP のファイル処理の、ごく一部にすぎません。もっと複雑な関数 (tidy_parse_file や xml_parse など) は、それぞれ HTML 文書や XML 文書を処理する上で役立ちます。こういった特定の関数の動作の詳細については、参考文献を参照してください。こうした種類のファイルを扱う場合には、これらの関数について調べる価値があるかもしれません。しかしこの記事では、考えられるファイル・タイプのすべてを詳細に検討する代わりに、ここまでに説明した関数を扱う際の、一般的で適切なルールをいくつか説明することにします。


適切なプラクティス

プログラムの中のすべてが計画通り動作すると思うべきではありません。例えば、探しているファイルが移動されてしまったらどうでしょう。パーミッションが変更され、内容を読めなくなったとしたらどうでしょう。これらを事前にチェックするためには、file_exists と is_readable を使います。

リスト 7 file_exists と is_readable を使う
$filename = "myfile";
if (file_exists($filename) && is_readable ($filename)) {
$fh = fopen($filename, "r");
# Processing
fclose($fh);
}

しかし実際には、おそらくこうしたコードでは大げさすぎます。fopen の戻り値を処理する方が単純で、しかも正確です。

if ($fh = fopen($filename, "r")) {
# Processing
fclose($fh);
}

fopen は失敗すると False を返すため、これを利用することで、無事にファイルを開けた場合にのみファイル処理が行われるように保証することができます。もちろん、ファイルが存在しない、あるいは読み取れない場合には、戻り値は負の値になるはずです。これはつまり、この 1 つのチェックのみで、突き当たる可能性のあるすべての問題に対応できるということです。あるいは、もしファイルが開けなかったらプログラムを終了させる、あるいはエラー・メッセージを表示させる、ということもできます。

fopen と同様、file_get_contents や file、readfile は、ファイルを開けなかったりファイル処理が失敗したりすると、どれも False を返します。fgets、fgetss、fread、fscanf、fclose などの関数も、エラーが起きると False を返します。fclose を除くと、言うまでもなく皆さんは、既にこれらの関数の戻り値を処理しているかもしれません。fclose の場合は、もしファイル・ハンドルが適切に閉じなかったら、ほとんどどうにもなりません。そのため、通常は fclose の戻り値をチェックする必要はありません。


目的に合った関数を選ぶ

PHP には、ファイルを読み取って解析するための効果的な方法が無数にあります。ほとんどの場合は fread のような古典的な関数が最適かもしれず、あるいは readfile の単純さの方が (目的にかないさえすれば) 魅力的かもしれません。すべては皆さんが何を実現したいのかに依存するのです。

大量のデータを処理する場合には、おそらく fscanf の方が、例えば file コマンドの後に split と sprintf コマンドを従えた場合よりも便利で効率的でしょう。それとは対照的に、大量のテキストをほとんど変更せずに単純にエコーする場合には、file や file_get_contents、あるいは readfile などの方が適切かもしれません。キャッシングに、さらには間に合わせのプロキシー・サーバーを作るために PHP を使う場合が、正にそうした例にあたると思います。

PHP にはファイルを扱うためのツールが大量に用意されています。そうした各ツールに慣れ、どのツールが皆さんのプロジェクトにとって最適かを学んでください。選択肢は豊富です。それらをうまく使いこなし、PHP を使ったファイル処理を楽しんでください。

参考文献

学ぶために

  • PHP.net では、すべてのコマンドの説明を含め、PHP のすべてを知ることができます。
  • PHP by example, Part 1」を読み、複雑で強力な Web 関連プログラムを PHP で構築するための方法を学んでください。
  • この記事では説明しなかった xml_parse 関数について学んでください。
  • この記事では説明しなかった tidy_parse_file 関数について学んでください。
  • PHP.net は PHP 開発者のためのリソースです。
  • Recommended PHP reading list」を調べてみてください。
  • developerWorks にある PHP関連 の記事を読んでください。
  • IBM developerWorks の PHP project resources を利用して PHP のスキルを磨いてください。
  • developerWorks podcasts では、ソフトウェア開発者のための興味深いインタビューや議論を聞くことができます。
  • developerWorks technical events and webcasts で最新情報を入手してください。
  • IBM オープンソース開発者にとって関心のある、世界中で今後開催される会議や業界展示会、ウェブキャスト、その他のイベントについて調べてみてください。
  • developerWorks の Open source ゾーンをご覧ください。オープンソース技術を使った開発や、IBM 製品でオープンソース技術を使用するためのハウ・ツー情報やツール、プロジェクトの更新情報など、豊富な情報が用意されています。

製品や技術を入手するために

  • 皆さんの次期オープンソース開発プロジェクトを IBM trial software を使って構築してください。ダウンロード、あるいは DVD で入手することができます。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source
ArticleID=249836
ArticleTitle=PHP でファイルを読み取るための正しい方法
publish-date=06132013