ファイルポインタの操作
そふぃのPHP入門 >> PHP実践リファレンス >> ファイル操作 >> ファイルポインタの操作

ファイルポインタの操作

ファイルポインタの意味

読み込みも書き込みも覚えたところでそろそろカウンターの1つも作れそうですが、前にファイルポインタの位置を把握しとけと言った覚えがあります。ファイルポインタの位置が分からないと期待通りの動きをしてくれず、自分が書いたスクリプトの動作が???となります。とりあえずこんなスクリプトを見て下さい。これはファイルポインタの操作ができなくて期待通りの動作をしてくれないダメスクリプト君です。

カウンターの話を出したついでですが、このコンテンツの最後の方でカウンター作りますので、そろそろカウント値記録用のファイルを作っておきます。ファイル名はここでは「count.txt」とします。ファイルの1行目に「0」とだけ書いて保存して下さい。パーミッションは「666」(606で動けばその方が好ましい)に設定しておいて下さい。

  1. <?php
  2. // 注)このサンプルではカウンターとしての役割を果たしません
  3. // どうせ間違ったサンプル例なのでエラー制御演算子もつけてません
  4.  
  5. $filename = 'count.txt'; // ファイル名を変数に格納
  6.  
  7. //上書きモードでファイルを開く
  8. $fp = fopen( $filename, "r+" ); // r+モードなので元のカウント値は残ってるはず
  9.  
  10. // ここで読み込むのは2-1バイト、つまり1バイト分しか読み込みません
  11. // どうしてかというと、間違ってるよ~~って言いたいだけのサンプルだからです
  12. // 実際にカウンター作る時はもう少し大きい数字を指定します
  13. $count = fgets( $fp, 2 ); // 元のカウント値を読み込んで・・・
  14.  
  15. fwrite( $fp, $count+1 ); // カウント値に+1した値をファイルに書き込む。
  16.  
  17. // 上書きモードで開いてるしちゃんと上書きされてるはず♪と思いながらファイルを閉じる
  18. fclose($fp);
  19.  
  20. // +1したカウント値を表示してみる。
  21. readfile( $filename, filesize($filename) );
  22. ?>

出力結果

01

「01」となってるでしょうが、何なんでしょうね?。あ、先頭に0つけて表示したいからちょうどいいや♪とか思わないで下さい。このスクリプトじゃカウンターの役割を果たしません。

試しにfgets()の長さ指定を10くらいまであげて試してみて下さい。変な動きをするカウンターになってるハズです。

これはファイルポインタの位置が把握できてない人がやってしまう間違いの典型です。という事で、どうすれば直るかはまだ説明しません。ファイルポインタの把握がちゃんと出来てる人は上記のスクリプトがどうしてそのような動きをするかちゃんと分かるでしょうからこの先の解説は必要ないでしょう。

ファイルポインタの動作を把握する

さて、分からない方はファイルポインタの動きをちゃんと理解してもらわないとこの先カウンター以上のスクリプトが組めなくなりますので、回りくどいですが上記のスクリプトでのファイルポインタの動きを見てみます。

  1. fopen()のモードが「r+」なのでファイルポインタはファイルの先頭。
  2. fgets()でファイルの内容を読み込んでいるので、ファイルポインタは、読み込んだ内容の最後に移動。
  3. fwrite()では読み込んだ内容(元のカウント値)の最後から書き込んでいる

分かりにくいかもしれませんが、ファイルポインタはfopen()時にはファイルの先頭にあるにも関わらず、内容を読み込んだ時点で「0」の後ろに移動してるんです。つまり、いくらfopen()のモードが「上書きモード」であってもすでに値のない場所から書き込みはじめているので、動作としては「追記」のようになってしまってるんです。

ちなみにfopen()のモードに「追記モード」というものがちゃんと存在します。追記したい時はそちらのモードでfopen()するようにして下さい。fopen()での追記モードは「a」です。「a」を指定するとファイルポインタがファイルの終端に置かれますので常に「追記モード」となります。

さて、今回は追記したくないスクリプトです。値を上書きしたいのに「r+」で開いてもファイルポインタのせいで不具合が出ているわけですが、これをどうしたらいいかは次から紹介する関数を見ながらじっくり考えてみて下さい。

現在のファイルポインタの位置を取得する―ftell関数

まずはファイルポインタの動作を理解するために便利な関数です。

「ファイルの先頭」にファイルポインタがあるというのはファイルの「0バイト目」という意味です。これを把握した上でftell()関数を使ってみましょう。ftell()関数は現在のファイルポインタの位置を先頭からのバイト数で取得してくれます。但し、引数にfopen()で取得したファイルポインタを指定しなければならないので、ftell()関数が使えるのはfopen()からfclose()までの間です。上記のスクリプトの各行に入れてファイルポインタの位置がどう動いているか見てみると実際の動きが分かります。位置を取得してくれるだけなので取得した値を「出力する」という処理も忘れないようにしないと、位置を見てみたくても見れません。

参考関数

  • ftell() ---- 現在のファイルポインタの位置を取得

書式

  • int ftell( resource handle )

成功した場合にはファイルポインタの位置をバイト数で返してくれますが、失敗した場合はFALSEを返します。

ファイルポインタの位置を先頭に戻す―rewind関数

さて、ファイルポインタの位置を先頭に戻すというとっても便利なrewind()という関数があります。rewind()関数の動作は「ファイルポインタを先頭に戻す」で固定されているので、「10バイト目に移動させたい」など、任意の場所に移動するためには他の関数を使いますが、ファイルポインタを先頭に戻したい時はrewind()関数が断然楽チンです。

こちらもfopen()で取得したファイルポインタを指定する必要があります。

参考関数

  • rewind() ---- ファイルポインタの位置を先頭に戻す

書式

  • bool rewind( resource handle )

さきほどfopen()の「a」モードを紹介しましたが、「a」(追記モード)でファイルを開いた場合はファイルポインタの位置に関わらずファイルに書き込まれるデータは常に「追記」となりますので注意して下さい。fopen()のモードの方が優先されてしまうのでrewind()関数の機能は事実上無視されてる事になります。

成功した場合にはTRUE、失敗した場合はFALSEを返します。

ファイルポインタの位置を移動する―fseek関数

先ほどちょっと触れた「ファイルポインタを任意の場所に移動する」って関数がこのfseek()です。rewind()関数のように引数1つで済むわけではありませんが、「10バイト目に移動したい」などのようにファイルポインタの位置を指定する事ができるようになります。

fseek()関数を使うと、例えば・・・「10バイト目までは固定のデータがあるから上書きしたくないけど、その後からのデータは上書きしたいんだよなぁ・・・」というような事もできます。

参考関数

  • fseek() ---- ファイルポインタの位置を移動する

第1引数にはファイルポインタを指定します。つまり、ここでもfopen()で取得したファイルポインタを指定する必要があります。

第2引数には移動させるバイト数を指定します。

第3引数が省略可能なオプション引数になってますが、これは移動の「開始位置」です。省略した場合は始点をファイルの先頭におきます。指定できる値は以下の通りです。

fseek()関数における始点
whence 説明
SEEK_SET 始点をファイルの最初に置く(デフォルト)
SEEK_CUR 始点を現在のファイルポインタの位置に置く
SEEK_END 始点をファイルの終端に置く

第3引数を省略した場合は自動的に「SEEK_SET」が指定されたとみなされるので、動作を分かりやすくしたい場合などを除き、通常はあまり「SEEK_SET」は指定されませんね。さらにSEEK_ENDでは開始位置がファイル終端なのでファイル終端(EOF)より手前に移動させたい場合はオフセット値(第2引数)に負の値を指定してファイルポインタを戻す必要がありあます。

補足ですが、ここで使える始点用の値は定数です。文字列ではなく定数なので指定する際にクォートは必要ありませんが、ではどこで定義されている定数かというとPHPに標準で組み込まれている定数です。つまりdefine()で自分で定義しなくてもいつでも使えるという事です。但し、第3引数がPHP4.0.0以降で追加されたので、PHP4.0.0以降でないと使えません。

成功した場合には0、失敗した場合は-1を返します。

fopen()の「a」モードで開いた場合はそちらの方が優先され、ファイルポインタの移動が無視される現象はrewind()関数と同じです。

さて、ファイルポインタの操作が出来る関数も紹介してきましたのでカウンターに必要な処理が分かったかと思います。

実際にカウンター作る前にrewind()関数とfseek()関数のサンプルです。fseek()関数でrewind()関数と同じ動作をさせるには以下のように書きます。

  1. fseek($fp,0);

つまり、以下の3つの記述は全て同じ動作をします。

  1. // すべて「ファイルポインタをファイルの先頭に戻す」という動作
  2. rewind($fp);
  3. fseek($fp,0);
  4. fseek($fp,0,SEEK_SET);

さて、ここまで紹介したら自力でちゃんと動くカウンターが作れます。カウンター値などの値をファイルに記録しておくにあたって、ファイルの競合等が起きて保存してあるデータが飛んでしまわないよう、次のページではファイルのロックについて必要性を解説します。