PHP stat() 函数

定义和用法

stat() 函数用于获取文件的状态信息。它返回一个包含文件统计信息的数组,包括文件大小、权限、修改时间等。

这个函数类似于Unix/Linux系统的stat命令,提供了文件的详细元数据信息。这些信息对于文件管理、监控和权限检查非常有用。

注意: stat() 函数的结果会被缓存。使用 clearstatcache() 函数清除缓存以获取最新信息。

语法

stat ( string $filename ) : array|false

参数说明:

  • $filename:要获取状态信息的文件路径(必需)

参数详解

参数 描述
filename

文件路径。必需参数。

  • 可以是相对路径或绝对路径
  • 必须指向已存在的文件或目录
  • 对于符号链接,返回链接指向的文件信息
  • 需要对该文件有读取权限

返回值

  • 成功时:返回包含文件信息的数组
  • 失败时:返回 false,通常是由于以下原因:
    • 文件不存在
    • 没有读取权限
    • 路径无效
返回数组结构

stat() 函数返回的数组包含以下元素:

数字索引 关联索引 描述 示例值
0 dev 设备号 2050
1 ino inode 号 123456
2 mode 文件类型和权限 33204 (十进制)
3 nlink 硬链接数 1
4 uid 所有者的用户ID 1000
5 gid 所有者的组ID 1000
6 rdev 设备类型(如果是inode设备) 0
7 size 文件大小(字节) 1024
8 atime 最后访问时间(Unix时间戳) 1609459200
9 mtime 最后修改时间(Unix时间戳) 1609459200
10 ctime 最后改变时间(Unix时间戳) 1609459200
11 blksize 文件系统I/O的块大小 4096
12 blocks 分配的512字节块数 8

示例

示例 1:基本用法

获取文件状态信息并显示:

<?php
$filename = 'example.txt';
$stats = stat($filename);

if ($stats !== false) {
    echo "文件: " . $filename . "<br>";
    echo "文件大小: " . $stats['size'] . " 字节<br>";
    echo "最后修改时间: " . date('Y-m-d H:i:s', $stats['mtime']) . "<br>";
    echo "最后访问时间: " . date('Y-m-d H:i:s', $stats['atime']) . "<br>";
    echo "所有者用户ID: " . $stats['uid'] . "<br>";
    echo "所有者组ID: " . $stats['gid'] . "<br>";
} else {
    echo "无法获取文件状态信息";
}
?>
示例 2:解析文件模式(权限)

解析 mode 字段获取文件类型和权限:

<?php
function parseFileMode($mode) {
    // 文件类型
    $fileTypes = [
        0xC000 => 'socket',
        0xA000 => 'symbolic link',
        0x8000 => 'regular file',
        0x6000 => 'block device',
        0x4000 => 'directory',
        0x2000 => 'character device',
        0x1000 => 'FIFO pipe'
    ];

    $type = 'unknown';
    foreach ($fileTypes as $mask => $name) {
        if (($mode & 0xF000) == $mask) {
            $type = $name;
            break;
        }
    }

    // 权限(八进制表示)
    $permissions = [
        ($mode & 0x0100) ? 'r' : '-', // 所有者读
        ($mode & 0x0080) ? 'w' : '-', // 所有者写
        ($mode & 0x0040) ? (($mode & 0x0800) ? 's' : 'x') : (($mode & 0x0800) ? 'S' : '-'), // 所有者执行/SUID
        ($mode & 0x0020) ? 'r' : '-', // 组读
        ($mode & 0x0010) ? 'w' : '-', // 组写
        ($mode & 0x0008) ? (($mode & 0x0400) ? 's' : 'x') : (($mode & 0x0400) ? 'S' : '-'), // 组执行/SGID
        ($mode & 0x0004) ? 'r' : '-', // 其他读
        ($mode & 0x0002) ? 'w' : '-', // 其他写
        ($mode & 0x0001) ? (($mode & 0x0200) ? 't' : 'x') : (($mode & 0x0200) ? 'T' : '-')  // 其他执行/粘滞位
    ];

    return [
        'type' => $type,
        'permissions' => implode('', $permissions),
        'mode_octal' => sprintf("%o", $mode & 07777)
    ];
}

$filename = 'test.php';
$stats = stat($filename);

if ($stats !== false) {
    $modeInfo = parseFileMode($stats['mode']);
    echo "文件: " . $filename . "<br>";
    echo "文件类型: " . $modeInfo['type'] . "<br>";
    echo "权限: " . $modeInfo['permissions'] . "<br>";
    echo "八进制权限: " . $modeInfo['mode_octal'] . "<br>";
}
?>
示例 3:监控文件变化

使用 stat() 监控文件是否被修改:

<?php
class FileMonitor {
    private $filename;
    private $lastStats;

    public function __construct($filename) {
        $this->filename = $filename;
        $this->updateStats();
    }

    public function updateStats() {
        clearstatcache(true, $this->filename);
        $this->lastStats = stat($this->filename);
        return $this->lastStats !== false;
    }

    public function hasChanged() {
        clearstatcache(true, $this->filename);
        $currentStats = stat($this->filename);

        if ($currentStats === false || $this->lastStats === false) {
            return false;
        }

        // 比较关键字段
        return (
            $currentStats['mtime'] !== $this->lastStats['mtime'] ||
            $currentStats['size'] !== $this->lastStats['size'] ||
            $currentStats['ino'] !== $this->lastStats['ino']
        );
    }

    public function getChangeDetails() {
        clearstatcache(true, $this->filename);
        $currentStats = stat($this->filename);

        if ($currentStats === false || $this->lastStats === false) {
            return null;
        }

        $changes = [];

        if ($currentStats['mtime'] !== $this->lastStats['mtime']) {
            $changes[] = '修改时间变化: ' .
                date('Y-m-d H:i:s', $this->lastStats['mtime']) . ' → ' .
                date('Y-m-d H:i:s', $currentStats['mtime']);
        }

        if ($currentStats['size'] !== $this->lastStats['size']) {
            $changes[] = '文件大小变化: ' .
                $this->lastStats['size'] . ' → ' .
                $currentStats['size'] . ' 字节';
        }

        if ($currentStats['atime'] !== $this->lastStats['atime']) {
            $changes[] = '访问时间变化';
        }

        return $changes;
    }
}

// 使用示例
$monitor = new FileMonitor('config.ini');
echo "开始监控文件: config.ini<br>";

// 模拟文件变化检测
if ($monitor->hasChanged()) {
    $changes = $monitor->getChangeDetails();
    echo "文件发生变化:<br>";
    foreach ($changes as $change) {
        echo "- " . $change . "<br>";
    }
    $monitor->updateStats();
} else {
    echo "文件未发生变化<br>";
}
?>
示例 4:目录信息统计

获取目录状态信息:

<?php
function getDirectoryStats($dirPath) {
    if (!is_dir($dirPath)) {
        return false;
    }

    $stats = stat($dirPath);

    if ($stats === false) {
        return false;
    }

    // 统计目录中的文件
    $files = scandir($dirPath);
    $fileCount = 0;
    $dirCount = 0;
    $totalSize = 0;

    foreach ($files as $file) {
        if ($file === '.' || $file === '..') continue;

        $fullPath = $dirPath . '/' . $file;
        if (is_dir($fullPath)) {
            $dirCount++;
        } else {
            $fileCount++;
            $fileStats = stat($fullPath);
            if ($fileStats !== false) {
                $totalSize += $fileStats['size'];
            }
        }
    }

    return [
        'directory_stats' => $stats,
        'contents' => [
            'total_items' => count($files) - 2, // 排除 . 和 ..
            'file_count' => $fileCount,
            'dir_count' => $dirCount,
            'total_size' => $totalSize
        ],
        'permissions' => substr(sprintf('%o', $stats['mode']), -4)
    ];
}

$dirInfo = getDirectoryStats('/var/www/html');
if ($dirInfo !== false) {
    echo "<h4>目录统计信息</h4>";
    echo "权限: " . $dirInfo['permissions'] . "<br>";
    echo "最后修改: " . date('Y-m-d H:i:s', $dirInfo['directory_stats']['mtime']) . "<br>";
    echo "文件数: " . $dirInfo['contents']['file_count'] . "<br>";
    echo "子目录数: " . $dirInfo['contents']['dir_count'] . "<br>";
    echo "总大小: " . number_format($dirInfo['contents']['total_size']) . " 字节<br>";
}
?>
示例 5:lstat() 与 stat() 的区别

比较 stat() 和 lstat() 对于符号链接的不同行为:

<?php
// 创建一个测试文件和一个指向它的符号链接
$original = 'original.txt';
$link = 'link_to_original.txt';

file_put_contents($original, 'This is the original file content');

if (!file_exists($link)) {
    symlink($original, $link);
}

echo "原始文件信息 (stat()):<br>";
$originalStats = stat($original);
if ($originalStats !== false) {
    echo "大小: " . $originalStats['size'] . " 字节<br>";
    echo "inode: " . $originalStats['ino'] . "<br>";
}

echo "<br>符号链接信息 (stat()):<br>";
$linkStats = stat($link);
if ($linkStats !== false) {
    echo "大小: " . $linkStats['size'] . " 字节<br>";
    echo "inode: " . $linkStats['ino'] . "<br>";
}

echo "<br>符号链接信息 (lstat()):<br>";
$lstatStats = lstat($link);
if ($lstatStats !== false) {
    echo "大小: " . $lstatStats['size'] . " 字节<br>";
    echo "inode: " . $lstatStats['ino'] . "<br>";
}

// 清理
unlink($original);
unlink($link);
?>

时间戳说明

stat() 返回三个不同的时间戳:

字段名 索引 描述 何时更新
atime 8 最后访问时间 (Access Time) 文件被读取时
mtime 9 最后修改时间 (Modification Time) 文件内容被修改时
ctime 10 最后改变时间 (Change Time) 文件元数据被改变时(权限、所有者等)
时间戳使用示例
<?php
$filename = 'document.txt';
$stats = stat($filename);

if ($stats !== false) {
    echo "文件: " . $filename . "<br>";
    echo "最后访问: " . date('Y-m-d H:i:s', $stats['atime']) . "<br>";
    echo "最后修改: " . date('Y-m-d H:i:s', $stats['mtime']) . "<br>";
    echo "最后改变: " . date('Y-m-d H:i:s', $stats['ctime']) . "<br>";

    // 计算文件年龄
    $now = time();
    $age_days = floor(($now - $stats['mtime']) / (60 * 60 * 24));
    echo "文件已存在: " . $age_days . " 天<br>";
}
?>

缓存注意事项

重要提示

PHP会对 stat() 的结果进行缓存以提高性能。在某些情况下,这可能导致获取到过时的信息。

清除缓存的方法
<?php
// 方法1:清除所有文件状态缓存
clearstatcache();

// 方法2:清除特定文件的缓存
$filename = 'data.txt';
clearstatcache(true, $filename);

// 方法3:在循环中正确使用
$files = ['file1.txt', 'file2.txt', 'file3.txt'];
foreach ($files as $file) {
    clearstatcache(true, $file); // 清除单个文件的缓存
    $stats = stat($file);
    if ($stats !== false) {
        echo $file . " 大小: " . $stats['size'] . "<br>";
    }
}
?>
缓存测试示例
<?php
$filename = 'test_cache.txt';
file_put_contents($filename, 'Initial content');

// 第一次获取
$stats1 = stat($filename);
echo "第一次获取大小: " . $stats1['size'] . "<br>";

// 修改文件但不清除缓存
file_put_contents($filename, 'Modified content, much longer than before');

// 第二次获取(可能使用缓存)
$stats2 = stat($filename);
echo "第二次获取大小(不清除缓存): " . $stats2['size'] . "<br>";

// 清除缓存后获取
clearstatcache(true, $filename);
$stats3 = stat($filename);
echo "清除缓存后获取大小: " . $stats3['size'] . "<br>";

unlink($filename);
?>

实际应用场景

场景1:文件备份系统
<?php
class BackupManager {
    private $backupDir;

    public function __construct($backupDir) {
        $this->backupDir = $backupDir;
        if (!is_dir($backupDir)) {
            mkdir($backupDir, 0755, true);
        }
    }

    public function needsBackup($sourceFile, $backupFile) {
        if (!file_exists($backupFile)) {
            return true;
        }

        clearstatcache(true, $sourceFile);
        clearstatcache(true, $backupFile);

        $sourceStats = stat($sourceFile);
        $backupStats = stat($backupFile);

        if ($sourceStats === false || $backupStats === false) {
            return true;
        }

        // 如果源文件比备份文件新,则需要备份
        return $sourceStats['mtime'] > $backupStats['mtime'];
    }

    public function createBackup($sourceFile) {
        $filename = basename($sourceFile);
        $backupFile = $this->backupDir . '/' . $filename . '.' . date('Ymd-His');

        if (copy($sourceFile, $backupFile)) {
            // 保持相同的修改时间
            $stats = stat($sourceFile);
            if ($stats !== false) {
                touch($backupFile, $stats['mtime']);
            }
            return $backupFile;
        }

        return false;
    }
}

// 使用示例
$backupManager = new BackupManager('/var/backups');
$sourceFile = '/var/www/config.php';

if ($backupManager->needsBackup($sourceFile, '/var/backups/config.php')) {
    $backupFile = $backupManager->createBackup($sourceFile);
    echo "已创建备份: " . $backupFile;
} else {
    echo "不需要备份,文件未修改";
}
?>
场景2:文件完整性检查
<?php
function getFileSignature($filename) {
    $stats = stat($filename);

    if ($stats === false) {
        return false;
    }

    // 创建文件的"签名",基于元数据
    $signature = [
        'ino' => $stats['ino'],
        'size' => $stats['size'],
        'mtime' => $stats['mtime']
    ];

    // 加上文件内容的哈希
    $signature['hash'] = md5_file($filename);

    return $signature;
}

function compareFileSignatures($file1, $file2) {
    $sig1 = getFileSignature($file1);
    $sig2 = getFileSignature($file2);

    if ($sig1 === false || $sig2 === false) {
        return false;
    }

    if ($sig1['ino'] === $sig2['ino'] && $sig1['size'] === $sig2['size']) {
        if ($sig1['hash'] === $sig2['hash']) {
            return 'identical'; // 完全相同的文件
        } else {
            return 'different_content'; // 相同inode和大小,但内容不同
        }
    }

    return 'different_files'; // 不同的文件
}

// 使用示例
$result = compareFileSignatures('file1.txt', 'file2.txt');
echo "文件比较结果: " . $result;
?>