stat() 函数用于获取文件的状态信息。它返回一个包含文件统计信息的数组,包括文件大小、权限、修改时间等。
这个函数类似于Unix/Linux系统的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 |
获取文件状态信息并显示:
<?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 "无法获取文件状态信息";
}
?>
解析 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>";
}
?>
使用 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>";
}
?>
获取目录状态信息:
<?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>";
}
?>
比较 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);
?>
<?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 "不需要备份,文件未修改";
}
?>
<?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;
?>