PHP Conference Japan 2024

stat

(PHP 4, PHP 5, PHP 7, PHP 8)

stat获取文件信息

描述

stat(string $filename): array|false

收集名为 filename 的文件的统计信息。如果 filename 是符号链接,则统计信息来自文件本身,而不是符号链接。在 PHP 7.4.0 之前,在 Windows NTS 版本中,sizeatimemtimectime 统计信息来自符号链接。

lstat()stat() 相同,只是它基于符号链接的状态。

参数

filename

文件路径。

返回值

stat()fstat() 结果格式
数字索引 关联索引 描述
0 dev 设备号 ***
1 ino inode 号 ****
2 mode inode 保护模式 *****
3 nlink 链接数
4 uid 所有者用户 ID *
5 gid 所有者组 ID *
6 rdev 设备类型,如果 inode 是设备
7 size 大小(以字节为单位)
8 atime 上次访问时间(Unix 时间戳)
9 mtime 上次修改时间(Unix 时间戳)
10 ctime 上次 inode 更改时间(Unix 时间戳)
11 blksize 文件系统 IO 的块大小 **
12 blocks 已分配的 512 字节块数 **

* 在 Windows 上,这将始终为 0

** 仅在支持 st_blksize 类型的系统上有效 - 其他系统(例如 Windows)返回 -1

*** 在 Windows 上,从 PHP 7.4.0 开始,这是包含文件的卷的序列号,这是一个 64 位 *无符号* 整数,因此可能会溢出。之前,对于 stat(),它是驱动器号的数字表示(例如 C:2),对于 lstat(),则为 0

**** 在 Windows 上,从 PHP 7.4.0 开始,这是与文件关联的标识符,这是一个 64 位 *无符号* 整数,因此可能会溢出。之前,它始终为 0

***** 在 Windows 上,可写权限位根据只读文件属性设置,并且对所有用户、组和所有者报告相同的值。与 is_writable() 不同,不会考虑 ACL。

mode 的值包含由多个函数读取的信息。以八进制表示,从右边开始,前三位由 chmod() 返回。下一位被 PHP 忽略。接下来的两位表示文件类型。

mode 文件类型
mode 八进制值 含义
0140000 套接字
0120000 符号链接
0100000 普通文件
0060000 块设备
0040000 目录
0020000 字符设备
0010000 FIFO
例如,普通文件可能是 0100644,目录可能是 0040755

如果发生错误,stat() 返回 false

注意: 由于 PHP 的整数类型是有符号的,并且许多平台使用 32 位整数,因此对于大于 2GB 的文件,某些文件系统函数可能会返回意外的结果。

错误/异常

失败时,会发出 E_WARNING

变更日志

版本 描述
7.4.0 在 Windows 上,设备号现在是包含文件的卷的序列号,inode 号是与文件关联的标识符。
7.4.0 符号链接的 sizeatimemtimectime 统计信息始终是目标文件的统计信息。之前在 Windows 的 NTS 版本中并非如此。

范例

示例 #1 stat() 示例

<?php
/* 获取文件状态 */
$stat = stat('C:\php\php.exe');

/*
* 打印文件访问时间,这与
* 调用 fileatime() 相同
*/
echo '访问时间: ' . $stat['atime'];

/*
* 打印文件修改时间,这与
* 调用 filemtime() 相同
*/
echo '修改时间: ' . $stat['mtime'];

/* 打印设备号 */
echo '设备号: ' . $stat['dev'];
?>

示例 #2 将 stat() 信息与 touch() 一起使用

<?php
/* 获取文件状态 */
$stat = stat('C:\php\php.exe');

/* 获取状态信息失败了吗? */
if (!$stat) {
echo
'stat() 调用失败...';
} else {
/*
* 我们希望访问时间为
* 当前访问时间后的 1 周。
*/
$atime = $stat['atime'] + 604800;

/* 修改文件 */
if (!touch('some_file.txt', time(), $atime)) {
echo
'修改文件失败...';
} else {
echo
'touch() 返回成功...';
}
}
?>

备注

注意:

请注意,不同文件系统的时间分辨率可能不同。

注意: 此函数的结果会被缓存。有关更多详细信息,请参阅 clearstatcache()

提示

从 PHP 5.0.0 开始,此函数也可以与某些 URL 封装器一起使用。请参考 支持的协议和封装器 以确定哪些封装器支持 stat() 系列的功能。

参见

添加笔记

用户贡献笔记 18 条笔记

14
webmaster at askapache dot com
16 年前
这是一个基于
许多用户提交的代码片段和
@ http://www.askapache.com/security/chmod-stat.html 的增强版“stat”函数。

给它一个文件名,它将返回一个类似于 stat 的数组。

<?php

function alt_stat($file) {

clearstatcache();
$ss=@stat($file);
if(!
$ss) return false; //无法获取文件状态

$ts=array(
0140000=>'ssocket',
0120000=>'llink',
0100000=>'-file',
0060000=>'bblock',
0040000=>'ddir',
0020000=>'cchar',
0010000=>'pfifo'
);

$p=$ss['mode'];
$t=decoct($ss['mode'] & 0170000); // 文件类型位

$str =(array_key_exists(octdec($t),$ts))?$ts[octdec($t)]{0}:'u';
$str.=(($p&0x0100)?'r':'-').(($p&0x0080)?'w':'-');
$str.=(($p&0x0040)?(($p&0x0800)?'s':'x'):(($p&0x0800)?'S':'-'));
$str.=(($p&0x0020)?'r':'-').(($p&0x0010)?'w':'-');
$str.=(($p&0x0008)?(($p&0x0400)?'s':'x'):(($p&0x0400)?'S':'-'));
$str.=(($p&0x0004)?'r':'-').(($p&0x0002)?'w':'-');
$str.=(($p&0x0001)?(($p&0x0200)?'t':'x'):(($p&0x0200)?'T':'-'));

$s=array(
'perms'=>array(
'umask'=>sprintf("%04o",@umask()),
'human'=>$str,
'octal1'=>sprintf("%o", ($ss['mode'] & 000777)),
'octal2'=>sprintf("0%o", 0777 & $p),
'decimal'=>sprintf("%04o", $p),
'fileperms'=>@fileperms($file),
'mode1'=>$p,
'mode2'=>$ss['mode']),

'owner'=>array(
'fileowner'=>$ss['uid'],
'filegroup'=>$ss['gid'],
'owner'=>
(
function_exists('posix_getpwuid'))?
@
posix_getpwuid($ss['uid']):'',
'group'=>
(
function_exists('posix_getgrgid'))?
@
posix_getgrgid($ss['gid']):''
),

'file'=>array(
'filename'=>$file,
'realpath'=>(@realpath($file) != $file) ? @realpath($file) : '',
'dirname'=>@dirname($file),
'basename'=>@basename($file)
),

'filetype'=>array(
'type'=>substr($ts[octdec($t)],1),
'type_octal'=>sprintf("%07o", octdec($t)),
'is_file'=>@is_file($file),
'is_dir'=>@is_dir($file),
'is_link'=>@is_link($file),
'is_readable'=> @is_readable($file),
'is_writable'=> @is_writable($file)
),

'device'=>array(
'device'=>$ss['dev'], //设备
'device_number'=>$ss['rdev'], //设备号,如果是设备。
'inode'=>$ss['ino'], //文件序列号
'link_count'=>$ss['nlink'], //链接计数
'link_to'=>($s['type']=='link') ? @readlink($file) : ''
),

'size'=>array(
'size'=>$ss['size'], //文件大小,以字节为单位。
'blocks'=>$ss['blocks'], //分配的512字节块数
'block_size'=> $ss['blksize'] //I/O 的最佳块大小。
),

'time'=>array(
'mtime'=>$ss['mtime'], //上次修改时间
'atime'=>$ss['atime'], //上次访问时间。
'ctime'=>$ss['ctime'], //上次状态更改时间
'accessed'=>@date('Y M D H:i:s',$ss['atime']),
'modified'=>@date('Y M D H:i:s',$ss['mtime']),
'created'=>@date('Y M D H:i:s',$ss['ctime'])
),
);

clearstatcache();
return
$s;
}

?>

|=---------[ 示例输出 ]

Array(
[perms] => Array
(
[umask] => 0022
[human] => -rw-r--r--
[octal1] => 644
[八进制] => 0644
[十进制] => 100644
[文件权限] => 33188
[模式1] => 33188
[模式2] => 33188
)

[文件类型] => 数组
(
[类型] => 文件
[类型(八进制)] => 0100000
[是否为文件] => 1
[是否为目录] =>
[是否为链接] =>
[是否可读] => 1
[是否可写] => 1
)

[所有者] => 数组
(
[文件所有者] => 035483
[文件组] => 23472
[所有者名称] => askapache
[组名称] => grp22558
)

[文件] => 数组
(
[文件名] => /home/askapache/askapache-stat/htdocs/ok/g.php
[真实路径] =>
[目录名] => /home/askapache/askapache-stat/htdocs/ok
[文件名] => g.php
)

[设备] => 数组
(
[设备] => 25
[设备号] => 0
[索引节点号] => 92455020
[链接计数] => 1
[链接目标] =>
)

[大小] => 数组
(
[大小] => 2652
[块数] => 8
[块大小] => 8192
)

[时间] => 数组
(
[修改时间] => 1227685253
[访问时间] => 1227685138
[状态改变时间] => 1227685253
[上次访问时间] => 2008年11月20日 23:38:58
[上次修改时间] => 2008年11月20日 23:40:53
[创建时间] => 2008年11月20日 23:40:53
)
)
11
askapache.com的webmaster
10年前
在GNU/Linux系统上,可以通过统计`/proc`目录下的硬链接数量来获取当前运行的进程数,方法如下:

$ stat -c '%h' /proc
118

在PHP中,也可以通过对`/proc`进行`stat`操作,并获取返回数组中的[3] 'nlink'(链接数)来实现相同的功能。

以下是我使用的函数,当多次调用时,它会执行`clearstatcache()`。

<?php

/**
* 返回正在运行的进程数
*
* @link https://php.net/clearstatcache
* @link https://php.net/stat stat语法说明。
* @author http://www.askapache.com/php/get-number-running-proccesses.html
* @return int
*/
function get_process_count() {
static
$ver, $runs = 0;

// 检查PHP版本是否支持clearstatcache参数,但只检查一次
if ( is_null( $ver ) )
$ver = version_compare( PHP_VERSION, '5.3.0', '>=' );

// 只在函数被多次调用时调用clearstatcache() */
if ( $runs++ > 0 ) { // 检查$runs是否大于0,然后将$runs加1。

// 如果PHP版本>= 5.3.0
if ( $ver ) {
clearstatcache( true, '/proc' );
} else {
// 如果PHP版本< 5.3.0
clearstatcache();
}
}

$stat = stat( '/proc' );

// 如果stat成功且nlink值存在,则返回它,否则返回0
return ( ( false !== $stat && isset( $stat[3] ) ) ? $stat[3] : 0 );
}

?>

示例 #1 get_process_count() 示例

<?php
$num_procs
= get_process_count();
var_dump( $num_procs );
?>

以上示例将输出

int(118)

这是正在运行的进程数。
8
smitelli.com的admin
19年前
Windows系统上的文件日期与夏令时存在一个重要(但鲜为人知)的问题。这会影响`stat()`返回的'atime'和'mtime'元素,还会影响其他与文件系统相关的函数,例如`fileatime()`和`filemtime()`。

在冬季(非夏令时期间),Windows会为给定文件报告某个时间戳。但是,当夏季到来并启用夏令时后,Windows会报告不同的时间戳!即使文件根本没有被修改,Windows也会在夏令时期间将其读取的每个时间戳向前移动整整一小时。

所有这些都源于微软决定使用一种过时的文件日期跟踪方法,以确保在10月份夏令时结束时“重复的小时”内没有歧义的时间,并保持与旧版FAT分区等的兼容性。可以在http://www.codeproject.com/datetime/dstbugs.asp找到关于这是什么/为什么是这样的出色描述。

值得注意的是,*nix平台不存在此问题。如果您尝试在平台之间移动跟踪文件时间戳的脚本,这可能会导致一些难以追踪的错误。

我花了不少时间尝试调试我自己的一个遇到此问题的脚本。我将文件修改时间存储在MySQL表中,然后使用这些信息查看自脚本上次运行以来哪些文件已被修改。在每次夏令时变更后,脚本看到的每个文件都被认为是自上次运行以来“已更改”的,因为所有时间戳都相差+/- 3600秒。

下面这个单行代码可能是你能想到的最不正确的修复方法之一,但它在生产环境中运行良好……假设`$file_date`是你刚从文件中读取的Unix时间戳

<?php
if (date('I') == 1) $file_date -= 3600;
?>

这将确保您使用的时间戳始终一致地报告,无论机器是否处于夏令时。
5
gmail.com的mail4rico
16 年前
回复“smitelli.com的admin”发布的笔记的第一行:
回复“smitelli.com的admin”发布的笔记

我认为你的转换反了。如果系统处于夏令时而文件不在夏令时,则应将一小时添加到`filemtime`。相反,如果文件时间是夏令时而当前操作系统时间不是,则应减去一小时。

这是一个简化后的更正版本
<?php
function getmodtime($file) { // 返回文件修改时间。
$mtime = filemtime($file);
// date('I') 如果夏令时开启返回1,否则返回0。
$diff = date('I')-date('I', $mtime);
// diff = 0 如果文件时间和操作系统时间都在相同的夏令时设置下
// diff = 1 如果操作系统是夏令时而文件不是
// diff = -1 如果文件是夏令时而操作系统不是
return $mtime + $diff*3600;
}
?>
这是一个测试
<?php
// 创建两个虚拟文件:
$file0 = 'file1.txt';
$file1 = 'file2.txt';
file_put_contents($file0, '');
file_put_contents($file1, '');

$time0=strtotime('Jan 1 2008 10:00'); echo 'Date0 (ST): ' . date(DATE_COOKIE, $time0)."\n";
$time1=strtotime('Aug 1 2008 10:00'); echo 'Date1 (DT): ' . date(DATE_COOKIE, $time1)."\n";
touch($file0, $time0); // 将file0设置为冬季(非夏令时)
touch($file1, $time1); // 将file1设置为夏季(夏令时)

$ftime0 = filemtime($file0);
$ftime1 = filemtime($file1);
echo
"\n未修正:\n";
echo
'文件 0: ' . ($ftime0-$time0) ."\n";
echo
'文件 1: ' . ($ftime1-$time1) ."\n";
// 如果你的系统调整了夏令时,那么上述值之一应该是3600或-3600,具体取决于一年中的时间

$ftime0 = getmodtime($file0); // 使用filemtime修正
$ftime1 = getmodtime($file1); // 使用filemtime修正
echo "\n修正后:\n";
echo
'文件 0: ' . ($ftime0-$time0) ."\n";
echo
'文件 1: ' . ($ftime1-$time1) ."\n";
// 修正后的两个值都应输出0。
?>

输出
------------------------------
(在夏季运行时)
------------------------------
Date0 (ST): 2008年1月1日,星期二 10:00:00 EST
Date1 (DT): 2008年8月1日,星期五 10:00:00 EDT

未修正
文件 0: -3600
文件 1: 0

修正后
文件 0: 0
文件 1: 0
------------------------------
(冬季运行时——日期省略)
------------------------------
未修正
文件 0: 0
文件 1:3600

修正后
文件 0: 0
文件 1: 0

回复“admin at smitelli dot com”发布的便签,将您的版本替换到我的测试中后,得到以下输出:
------------------------------
(夏季运行时——日期省略)
------------------------------
未修正
文件 0: -3600
文件 1: 0

修正后
文件 0:-7200
文件 1: 0
------------------------------
您可以看到,操作结果与预期相反。
5
[email protected]
19年前
回复“salisbm at hotmail dot com”发布的便签

S_IFDIR 不是单个位的标志。它是一个依赖于“S_IFMT”位掩码的常量。正如 stat.h 中所述,在与任何其他“S_IF...”常量比较之前,应将此位掩码应用于“mode”参数。

#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)

也就是说,这种方法是错误的

<?php
define
('S_IFDIR',040000);
if (
$mode & S_IFDIR)
{
/*
错误!
格式可能是 S_IFDIR,但也可能是
S_IFBLK、S_IFSOCK 或 S_IFWHT。
*/
}
?>

...而应该改为

<?php
define
('S_IFMT',0170000);
define('S_IFDIR',040000);
if (
S_IFDIR == ($mode & S_IFMT)) { /* ... */ }
?>

然而,正如“svend at svendtofte dot com”所指出的那样,还有“is_dir”函数可用于此目的,以及“is_file”和“is_link”函数可用于涵盖最常见的格式类型……
8
[email protected]
21 年前
我很好奇如何判断文件是否是目录……因此,我在http://www.hmug.org/man/2/stat.html上找到了关于模式位的以下信息
#define S_IFMT 0170000 /* 文件类型 */
#define S_IFIFO 0010000 /* 命名管道 (fifo) */
#define S_IFCHR 0020000 /* 字符设备 */
#define S_IFDIR 0040000 /* 目录 */
#define S_IFBLK 0060000 /* 块设备 */
#define S_IFREG 0100000 /* 普通文件 */
#define S_IFLNK 0120000 /* 符号链接 */
#define S_IFSOCK 0140000 /* 套接字 */
#define S_IFWHT 0160000 /* 白洞 */
#define S_ISUID 0004000 /* 设置用户 ID 以执行 */
#define S_ISGID 0002000 /* 设置组 ID 以执行 */
#define S_ISVTX 0001000 /* 使用后仍保存交换文本 */
#define S_IRUSR 0000400 /* 读取权限,所有者 */
#define S_IWUSR 0000200 /* 写入权限,所有者 */
#define S_IXUSR 0000100 /* 执行/搜索权限,所有者 */

请注意,这些数字采用八进制格式。然后,为了检查文件是否为目录,在调用 fstat 后,我执行

if ($fstats[mode] & 040000)
...这肯定是一个目录
3
[email protected]
25 年前
以下是 UNIX stat 手册页关于文件更改和文件修改之间区别的说明:

st_mtime 最后修改数据的时间。以下函数会更改此时间:creat()、mknod()、pipe()、utime() 和 write(2)。

st_ctime 文件状态最后更改的时间。以下函数会更改此时间:chmod()、chown()、creat()、link(2)、mknod()、pipe()、unlink(2)、utime() 和 write()。

因此,修改是指数据的更改,而更改也发生在修改文件权限等情况下。
3
JulieC
17 年前
“marting.dc AT gmail.com”提供的 dir_size 函数运行良好,但 $mas 变量未初始化。请在 while() 循环之前添加

$mas = 0;

3
[email protected]
18 年前
如果您想了解目录大小,此函数将对您有所帮助

<?php
function dir_size($dir)
{
$handle = opendir($dir);

while (
$file = readdir($handle)) {
if (
$file != '..' && $file != '.' && !is_dir($dir.'/'.$file)) {
$mas += filesize($dir.'/'.$file);
} else if (
is_dir($dir.'/'.$file) && $file != '..' && $file != '.') {
$mas += dir_size($dir.'/'.$file);
}
}
return
$mas;
}
echo
dir_size('DIRECTORIO').' Bytes';
?>
3
[email protected]
19年前
如果您的远程服务器上禁用了 ftp(以及相关的 sftp)协议,则很难弄清楚如何“stat”远程文件。以下方法对我有用

<?php

$conn
= ssh2_connect($host, 22);
ssh2_auth_password($conn, $user, $password);
$stream = ssh2_exec($conn, "stat $fileName > $remotedest");
ssh2_scp_recv($conn, $remotedest, $localdest);
$farray = file($localdest);
print_r($farray);
?>
3
[email protected]
15 年前
关于在 32 位系统上对大于 2GB 的文件进行 stat() 不起作用的问题,请注意,Linux 和 Windows 之间的行为似乎有所不同。在 Windows 下,无法知道这是否失败。

根据我的经验,在 Linux 下,对大于整数大小的文件执行 stat() 会生成警告并返回 false。但是,在 Windows 下,它会静默截断大小的高位,导致数字不正确。您唯一能知道它失败的情况是,如果截断恰好保留了符号位,从而导致大小为负。也就是说,_没有_可靠的方法知道它失败了。

filesize() 也是如此。

Tom
1
[email protected]
10年前
如果在挂载时未指定 noserverino 选项,则在 32 位系统上,stat() 可能无法在已挂载的 CIFS 上运行。例如

mount -t cifs -o user="user",password="password",noserverino //example.local/share /mnt/mount-point

基于 stat() 数据的其他函数(如文件时间函数和 is_dir())也会受到同样的影响。

发生这种情况是因为,如果不指定 noserverino 选项,则远程 inode 可能是基于 64 位的,因此本地系统无法处理它。
1
[email protected]
20 年前
关于如何确定文件是否是文件夹,还有一个方便的“is_dir”函数。
1
Ray.Paseur(有时使用 Gmail)
3 年前
此处提供了对“mode”位的良好解释:
https://php.net/manual/en/function.fileperms.php
0
Hellhound
16 年前
要忽略索引号或名称细节……请使用

list($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks)
= lstat($directory_element);
0
匿名用户
18 年前
回复“smitelli.com的admin”发布的笔记

我不确定这在一年四季都能正常工作,因为您必须根据实际文件本身以及系统的当前夏令时设置来修改相反的夏令时内外。

例如,使用 filemtime,stat 函数也是同样的方法。

<?php

$mtime
= filemtime($file);

if (
date('I') == 1) {
// Windows 夏令时启用,将标准时间
// 文件调整回真实的UTC时间。
if (date('I', $mtime) == 0) {
$mtime -= 3600;
}
} else {
// Windows 夏令时禁用,将夏令时
// 文件调整到真实的UTC时间。
if (date('I', $mtime) == 1) {
$mtime += 3600;
}
}

echo
gmdate('Y-m-d H:i:s', $mtime);

?>

这只是另一个例子,说明为什么不应在服务器机房使用 Windows。
-2
antonixyz at gmx dot net
16 年前
<?php
$stat
= stat($filepath);
$mode = $stat[2];
?>
与以下代码等效
<?php $mode = fileperms($filepath); ?>

至少在我的 Linux 机器上是如此。
-4
匿名
20 年前
如果2GB的限制让你抓狂,你可以使用这个完整的hack。用它替换filesize()

function file_size($file) {
$size = filesize($file);
if ( $size == 0)
$size = exec("ls -l $file | awk '{print $5}'");
return $size;
}
To Top