【PHP8.x】readdir()関数の使い方
readdir関数の使い方について、初心者にもわかりやすく解説します。
基本的な使い方
readdir関数は、ディレクトリハンドルからエントリを読み込む関数です。この関数は、opendir関数で開かれたディレクトリハンドルを引数に取り、そのディレクトリ内の次のファイルまたはディレクトリの名前を返します。readdir関数を使用することで、ディレクトリの内容を順番に取得し、ファイル名やディレクトリ名を処理することができます。
readdir関数は、ディレクトリの終端に達した場合、またはエラーが発生した場合は、falseを返します。そのため、通常はwhileループの中で使用され、返り値がfalseになるまでディレクトリ内のエントリを読み込みます。
readdir関数が返すファイル名には、カレントディレクトリを示す「.」と、親ディレクトリを示す「..」も含まれます。これらの特殊なエントリを処理するかどうかは、アプリケーションの要件に応じて決定する必要があります。
例として、次のコードはディレクトリの内容をリスト表示します。
<?php
$dir = "/path/to/your/directory";
if (is_dir($dir)) {
if ($dh = opendir($dir)) {
while (($file = readdir($dh)) !== false) {
echo "filename: " . $file . "<br>";
}
closedir($dh);
}
}
?>
この例では、まずopendir関数でディレクトリを開き、readdir関数でファイル名を順番に取得しています。取得したファイル名をechoで表示し、最後にclosedir関数でディレクトリハンドルを閉じます。readdir関数は、システムエンジニアがディレクトリ内のファイルを操作する際に、基本的な機能を提供する重要な関数の一つです。
構文(syntax)
1<?php 2 3$dir_handle = opendir('/path/to/your/directory'); 4 5if ($dir_handle) { 6 while (false !== ($file_name = readdir($dir_handle))) { 7 // ここで $file_name を使った処理を行う 8 echo $file_name . PHP_EOL; 9 } 10 closedir($dir_handle); 11} 12 13?>
引数(parameters)
$dir_handle
- resource $dir_handle: 読み取るディレクトリへのリソースハンドル
戻り値(return)
string|false
ディレクトリから次のエントリの名前を文字列として返します。エントリがない場合は false を返します。
サンプルコード
PHP readdir でディレクトリを再帰的にリストアップする
1<?php 2 3/** 4 * 指定されたディレクトリの内容を再帰的にリストアップします。 5 * ファイルとディレクトリを区別して、階層構造をインデントで表現します。 6 * 7 * @param string $dirPath 探索を開始するディレクトリのパス。 8 * @param int $indent 現在の階層レベルを示すインデント数(内部使用)。 9 * @return void 10 */ 11function listDirectoryContents(string $dirPath, int $indent = 0): void 12{ 13 // ディレクトリが存在しないか、読み取れない場合はエラーを出力して終了 14 if (!is_dir($dirPath) || !is_readable($dirPath)) { 15 echo str_repeat(" ", $indent) . "Error: Cannot access directory '{$dirPath}'\n"; 16 return; 17 } 18 19 // ディレクトリハンドルを開く 20 $handle = opendir($dirPath); 21 22 // ディレクトリハンドルが開けなかった場合はエラーを出力して終了 23 if ($handle === false) { 24 echo str_repeat(" ", $indent) . "Error: Failed to open directory '{$dirPath}'\n"; 25 return; 26 } 27 28 // 現在のディレクトリ名を表示 29 echo str_repeat(" ", $indent) . "Directory: " . basename($dirPath) . "/\n"; 30 31 // ディレクトリ内のエントリを一つずつ読み込む 32 while (($entry = readdir($handle)) !== false) { 33 // カレントディレクトリ(.)と親ディレクトリ(..)はスキップ 34 if ($entry === '.' || $entry === '..') { 35 continue; 36 } 37 38 // 完全なパスを構築 39 $fullPath = $dirPath . DIRECTORY_SEPARATOR . $entry; 40 // 次のレベルのインデント文字列 41 $indentStr = str_repeat(" ", $indent + 1); 42 43 // エントリがディレクトリの場合 44 if (is_dir($fullPath)) { 45 // 再帰的に自身を呼び出して、そのディレクトリの内容をリストアップ 46 listDirectoryContents($fullPath, $indent + 1); 47 } else { 48 // エントリがファイルの場合、ファイル名を表示 49 echo $indentStr . "File: " . $entry . "\n"; 50 } 51 } 52 53 // ディレクトリハンドルを閉じる 54 closedir($handle); 55} 56 57// --- 単体で動作可能なコード例 --- 58 59// テスト用のディレクトリを再帰的に削除するヘルパー関数 60function rrmdir(string $dir): void 61{ 62 if (is_dir($dir)) { 63 $objects = scandir($dir); 64 foreach ($objects as $object) { 65 if ($object != "." && $object != "..") { 66 if (is_dir($dir . DIRECTORY_SEPARATOR . $object)) { 67 rrmdir($dir . DIRECTORY_SEPARATOR . $object); 68 } else { 69 unlink($dir . DIRECTORY_SEPARATOR . $object); 70 } 71 } 72 } 73 rmdir($dir); 74 } 75} 76 77// テスト用のディレクトリパスを設定 78$testDir = __DIR__ . DIRECTORY_SEPARATOR . 'test_recursive_dir'; 79 80// 以前のテストディレクトリがあれば削除してクリーンアップ 81if (file_exists($testDir)) { 82 rrmdir($testDir); 83} 84 85// テスト用のディレクトリ構造を作成 86mkdir($testDir); 87mkdir($testDir . DIRECTORY_SEPARATOR . 'sub_dir1'); 88mkdir($testDir . DIRECTORY_SEPARATOR . 'sub_dir2'); 89mkdir($testDir . DIRECTORY_SEPARATOR . 'sub_dir1' . DIRECTORY_SEPARATOR . 'nested_dir'); 90 91file_put_contents($testDir . DIRECTORY_SEPARATOR . 'file1.txt', 'This is file1.'); 92file_put_contents($testDir . DIRECTORY_SEPARATOR . 'sub_dir1' . DIRECTORY_SEPARATOR . 'file2.log', 'Log entry.'); 93file_put_contents($testDir . DIRECTORY_SEPARATOR . 'sub_dir1' . DIRECTORY_SEPARATOR . 'nested_dir' . DIRECTORY_SEPARATOR . 'file3.json', '{"key": "value"}'); 94file_put_contents($testDir . DIRECTORY_SEPARATOR . 'sub_dir2' . DIRECTORY_SEPARATOR . 'file4.csv', 'header1,header2\nvalue1,value2'); 95 96echo "--- Listing contents of '{$testDir}' ---\n"; 97// 再帰的なディレクトリリストアップを実行 98listDirectoryContents($testDir); 99echo "--- End of listing ---\n"; 100 101// 作成したテストディレクトリ構造をクリーンアップ 102rrmdir($testDir); 103 104?>
PHPのreaddir関数は、開かれたディレクトリから一つずつファイルやディレクトリの名前を読み込むための関数です。引数$dir_handleには、事前にopendir関数で取得したディレクトリのリソースを渡します。戻り値は、読み込んだエントリの名前を文字列として返しますが、ディレクトリの終わりに達した場合はfalseを返します。
提供されたサンプルコードでは、このreaddir関数を利用して、指定されたディレクトリの内容を再帰的にリストアップするlistDirectoryContents関数を実装しています。まずopendirでディレクトリを開き、readdirをループで呼び出してディレクトリ内の各エントリを順に取得します。取得したエントリが別のディレクトリであれば、listDirectoryContents関数が自分自身を呼び出し(再帰)、さらにそのサブディレクトリの内容を深く探索します。エントリがファイルであれば、そのファイル名をインデント付きで表示します。この処理により、ディレクトリの階層構造を視覚的に表現しています。すべてのエントリを読み終えたら、closedirでディレクトリハンドルを閉じます。このような再帰的なディレクトリ走査は、ファイルシステム操作の基本的なパターンであり、システム開発で非常に重要な技術です。
readdir関数を使用する際は、必ずopendirで開いたディレクトリハンドルを処理後にclosedirで閉じなければ、システムリソースを消費し続ける原因となります。また、readdirはカレントディレクトリ(.)と親ディレクトリ(..)も返すため、これらをスキップしないと再帰処理が無限ループに陥る危険性があります。ディレクトリが存在しない場合やアクセスできない場合に備え、opendirの戻り値をチェックするなど、適切なエラーハンドリングを行うことが重要です。パスの区切り文字には、OSに依存しないDIRECTORY_SEPARATOR定数を利用することをお勧めします。このような再帰処理では、深く潜りすぎるとスタックオーバーフローやパフォーマンスの問題が発生する可能性もあります。
PHPでreaddirでファイル更新日時順にソートする
1<?php 2 3/** 4 * 指定されたディレクトリ内のファイルを更新日時でソートして表示します。 5 * 6 * readdir 関数を使用してディレクトリからエントリを読み込み、 7 * filemtime 関数でファイルの最終更新日時を取得し、 8 * usort 関数でファイルを更新日時(新しい順)にソートします。 9 * 10 * @param string $directoryPath 検索するディレクトリのパス。 11 * @return void 12 */ 13function displayFilesSortedByDate(string $directoryPath): void 14{ 15 // ディレクトリが存在し、読み取り可能か確認します。 16 if (!is_dir($directoryPath)) { 17 echo "エラー: ディレクトリ '{$directoryPath}' が見つからないか、アクセスできません。\n"; 18 return; 19 } 20 21 $files = []; // ファイル情報(パスと更新日時)を格納する配列 22 23 // ディレクトリを開きます。 24 $dirHandle = opendir($directoryPath); 25 26 if ($dirHandle === false) { 27 echo "エラー: ディレクトリ '{$directoryPath}' を開けませんでした。\n"; 28 return; 29 } 30 31 // ディレクトリ内のエントリを一つずつ読み込みます。 32 // readdir はディレクトリ内のファイル名またはディレクトリ名を返します。 33 while (($entry = readdir($dirHandle)) !== false) { 34 // カレントディレクトリ (.) と親ディレクトリ (..) を表す特殊なエントリを除外します。 35 if ($entry === '.' || $entry === '..') { 36 continue; 37 } 38 39 // 完全なファイルパスを構築します。 40 // DIRECTORY_SEPARATOR はOSに応じた区切り文字(Windowsでは'\\'、Linuxでは'/')です。 41 $filePath = rtrim($directoryPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $entry; 42 43 // エントリが通常のファイルであり、かつ読み取り可能か確認します。 44 if (is_file($filePath) && is_readable($filePath)) { 45 // ファイルの最終更新日時をUNIXタイムスタンプ形式で取得します。 46 $modificationTime = filemtime($filePath); 47 48 // 更新日時が正常に取得できた場合、配列に追加します。 49 if ($modificationTime !== false) { 50 $files[] = [ 51 'path' => $filePath, 52 'mtime' => $modificationTime, 53 ]; 54 } else { 55 // 更新日時が取得できなかった場合の警告を出力します。 56 echo "警告: ファイル '{$filePath}' の更新日時を取得できませんでした。\n"; 57 } 58 } 59 } 60 61 // ディレクトリを閉じます。 62 closedir($dirHandle); 63 64 // 更新日時(mtime)で降順(新しいものが先)にソートします。 65 // usort はユーザー定義の比較関数を使って配列をソートします。 66 // PHP 7以降の宇宙船演算子 (<=>) を使用して、比較を簡潔に記述します。 67 // $b['mtime'] <=> $a['mtime'] は降順(大きい値が先)、$a['mtime'] <=> $b['mtime'] は昇順(小さい値が先)になります。 68 usort($files, function (array $a, array $b): int { 69 return $b['mtime'] <=> $a['mtime']; 70 }); 71 72 // ソートされたファイル情報を表示します。 73 if (empty($files)) { 74 echo "ディレクトリ '{$directoryPath}' にファイルが見つかりませんでした。\n"; 75 } else { 76 echo "ディレクトリ '{$directoryPath}' 内のファイルを更新日時でソート (新しい順):\n"; 77 foreach ($files as $file) { 78 // UNIXタイムスタンプを読みやすい日付形式(例: YYYY-MM-DD HH:MM:SS)に変換して表示します。 79 echo " " . date('Y-m-d H:i:s', $file['mtime']) . " - " . $file['path'] . "\n"; 80 } 81 } 82} 83 84// --- 使用例 --- 85// 現在のスクリプトがあるディレクトリを対象とします。 86// このディレクトリにいくつかのテキストファイルなどを作成して実行してみてください。 87$targetDirectory = __DIR__; 88displayFilesSortedByDate($targetDirectory); 89 90// 例: 別のディレクトリを指定する場合 91// $anotherDirectory = '/var/log'; // Linuxのログディレクトリなど 92// displayFilesSortedByDate($anotherDirectory); 93 94?>
このサンプルコードは、PHPのreaddir関数を用いて、指定されたディレクトリ内のファイルを更新日時が新しい順にソートして表示する方法を示しています。まず、opendir関数で対象ディレクトリを開き、その結果として得られる「ディレクトリハンドル」を取得します。readdir関数はこのディレクトリハンドルを引数として受け取り、ディレクトリ内のファイル名やサブディレクトリ名を一つずつ文字列として返します。すべてのエントリを読み終えるとfalseを返します。
コードでは、readdirで取得したエントリが通常のファイルであり、かつ特殊なエントリ(.や..)でないことを確認した後、filemtime関数を使ってファイルの最終更新日時(UNIXタイムスタンプ)を取得します。これらのファイルパスと更新日時を一時的な配列に格納します。すべてのエントリを読み込んだら、closedir関数でディレクトリを閉じます。最後に、usort関数とカスタムの比較ロジックを使用して、収集したファイル情報を更新日時で降順(新しいものが先)にソートし、整形された日時とともにファイルパスを画面に出力します。これにより、ディレクトリ内のファイルを更新順で簡単に確認できます。
このコードでは、opendirで開いたディレクトリハンドルを必ずclosedirで閉じることが重要です。閉じ忘れるとシステムのリソースを消費し続ける原因となります。readdir関数は、カレントディレクトリと親ディレクトリを示す特殊なエントリ「.」と「..」を返すため、これらを適切に除外する処理が必要です。ファイルシステム操作を行う関数は、失敗時にfalseを返すことが多いため、opendirやfilemtimeなどの戻り値を常に確認し、エラーハンドリングを行う習慣をつけましょう。パス結合にはDIRECTORY_SEPARATORを利用することで、異なるOS環境でも正しく動作するようになります。また、非常に多くのファイルが存在するディレクトリを扱う場合、メモリの使用量に注意が必要です。
readdir と scandir でファイル一覧を取得する
1<?php 2 3/** 4 * ディレクトリ内のファイルをリスト表示する例。 5 * readdir と scandir の比較を行います。 6 */ 7 8// readdir を使用した例 9function listFilesReaddir(string $directory): void 10{ 11 echo "readdir を使用:\n"; 12 if (($handle = opendir($directory)) !== false) { 13 while (($file = readdir($handle)) !== false) { 14 if ($file != "." && $file != "..") { // "." と ".." は現在のディレクトリと親ディレクトリなので除外 15 echo $file . "\n"; 16 } 17 } 18 closedir($handle); 19 } else { 20 echo "ディレクトリを開けませんでした。\n"; 21 } 22} 23 24// scandir を使用した例 25function listFilesScandir(string $directory): void 26{ 27 echo "\nscandir を使用:\n"; 28 $files = scandir($directory); 29 if ($files !== false) { 30 foreach ($files as $file) { 31 if ($file != "." && $file != "..") { // "." と ".." は現在のディレクトリと親ディレクトリなので除外 32 echo $file . "\n"; 33 } 34 } 35 } else { 36 echo "ディレクトリをスキャンできませんでした。\n"; 37 } 38} 39 40// 使用例 41$directory = './'; // 現在のディレクトリ 42listFilesReaddir($directory); 43listFilesScandir($directory); 44 45?>
このサンプルコードは、PHPのreaddir関数とscandir関数を使用して、ディレクトリ内のファイル一覧を取得する方法を比較しています。readdir関数は、ディレクトリハンドル($dir_handle)を引数に取り、ディレクトリ内のファイル名を1つずつ返します。ファイルの読み込みが完了するとfalseを返します。opendir関数でディレクトリを開き、closedir関数で閉じる必要があります。一方、scandir関数は、ディレクトリ名を引数に取り、ディレクトリ内のファイル名(配列形式)を一度に返します。
サンプルコードでは、listFilesReaddir関数とlistFilesScandir関数を定義し、それぞれreaddirとscandirを使用してファイル一覧を表示しています。どちらの関数も、.(現在のディレクトリ)と..(親ディレクトリ)を除外しています。readdirを使用する場合は、opendirでディレクトリを開き、readdirでファイル名を1つずつ取得し、closedirでディレクトリを閉じるという手順が必要です。scandirを使用する場合は、scandir関数を呼び出すだけでファイル名の配列を取得できます。
readdir関数は、ディレクトリ内のファイルを順に処理する際に便利ですが、ファイル名の一覧をまとめて取得したい場合は、scandir関数の方が簡潔に記述できます。どちらの関数も、ディレクトリが存在しない場合やアクセス権がない場合は、エラーが発生する可能性があります。エラー処理を適切に行うことが重要です。このサンプルコードは、現在のディレクトリ(./)を対象に、それぞれの関数の使用例を示しています。
readdir関数とscandir関数の比較例ですね。readdirはディレクトリハンドルを引数に取り、ファイル名を一つずつ返します。そのため、opendir関数でディレクトリハンドルを取得し、closedir関数で閉じる必要があります。scandirはディレクトリパスを引数に取り、ファイル名の配列を返します。
どちらの関数も、カレントディレクトリ(.)と親ディレクトリ(..)を返すため、通常はこれらを除外する必要があります。readdirはファイルが見つからない場合にfalseを返すのに対し、scandirはディレクトリのスキャンに失敗した場合にfalseを返します。エラー処理を確実に行いましょう。scandirの方が手軽ですが、メモリ使用量が多い点に注意が必要です。
PHP readdirでディレクトリ内容を順番に読み込む
1<?php 2 3/** 4 * ディレクトリ内のファイルを順番に読み込むサンプルコード 5 */ 6function readDirectoryContents(string $directoryPath): void 7{ 8 // ディレクトリハンドルをオープン 9 if ($dh = opendir($directoryPath)) { 10 // ファイルを順番に読み込む 11 while (($file = readdir($dh)) !== false) { 12 echo "filename: " . $file . PHP_EOL; 13 } 14 // ディレクトリハンドルをクローズ 15 closedir($dh); 16 } else { 17 echo "ディレクトリを開けませんでした。"; 18 } 19} 20 21// サンプルの実行 22$directoryPath = "./"; // 現在のディレクトリ 23readDirectoryContents($directoryPath); 24 25?>
このPHPのサンプルコードは、readdir()関数を使用して、指定されたディレクトリ内のファイルやディレクトリを順番に読み込み、ファイル名を表示するものです。
まず、readDirectoryContents()関数は、引数としてディレクトリのパス($directoryPath)を受け取ります。この関数内で、opendir()関数を使ってディレクトリハンドル($dh)を開きます。opendir()は、ディレクトリを開くことができた場合にディレクトリハンドルを返し、失敗した場合はfalseを返します。
次に、whileループの中でreaddir($dh)関数を使って、ディレクトリハンドルからファイル名またはディレクトリ名を順番に読み込みます。readdir()関数は、成功した場合はファイル名またはディレクトリ名を文字列で返し、ディレクトリの終わりに達した場合、またはエラーが発生した場合はfalseを返します。ループは、readdir()がfalseを返すまで繰り返されます。読み込まれたファイル名($file)は、echo文を使って表示されます。
最後に、closedir($dh)関数を使ってディレクトリハンドルを閉じ、リソースを解放します。ディレクトリを開くことができなかった場合は、「ディレクトリを開けませんでした。」というエラーメッセージが表示されます。
サンプルコードでは、現在のディレクトリ("./")を指定してreadDirectoryContents()関数を実行し、そのディレクトリ内のファイルとディレクトリを順番に表示しています。readdir()関数は、ディレクトリの内容を特定の順序で返すことを保証していません。
readdir関数を使う際の注意点です。readdirはディレクトリ内のエントリを順番に返しますが、順番はファイルシステムに依存するため、保証されません。ソートが必要な場合は、別途sort関数などを利用してください。.(現在のディレクトリ)と..(親ディレクトリ)も含まれるため、不要な場合は明示的に除外する必要があります。readdirは、読み込みに失敗した場合falseを返すため、厳密等価演算子(!==)でfalseかどうかを判定してください。型が異なる0や空文字列との比較では誤った判定になる可能性があります。また、opendirで開いたディレクトリハンドルは、必ずclosedirで閉じるようにしてください。