4. 文件属性操作¶
在Linux系统中,秉持“一切皆文件”的核心设计哲学,文件并非仅存储业务数据的载体, 还附带了大量用于描述文件特征的元数据(Metadata),也就是文件属性。 这些属性是操作系统对文件进行管理、权限管控、索引定位的核心依据, 涵盖文件类型、存储大小、访问权限、所属用户与用户组、各类时间戳、索引节点(inode)、链接数等关键信息。 在Linux应用开发、系统运维、嵌入式开发等场景中,获取并解析文件属性是高频刚需操作, 无论是文件合法性校验、权限判断、状态监控,还是日志审计、数据统计,都离不开文件属性操作。
4.1. 文件属性基础¶
4.1.1. 文件属性存储与获取原理¶
Linux文件的元数据并非存储在文件内容中,而是存放在索引节点(inode)中, inode是文件系统对文件进行管理的基本单元,每个文件对应唯一的inode号,系统通过inode号定位文件、读取文件属性。 用户层无法直接访问inode,需通过内核提供的系统调用函数获取属性信息,stat、fstat、lstat就是用于获取文件inode信息的核心系统调用, 内核会将inode中的属性信息填充至指定结构体,供用户层解析使用。
4.1.2. 核心属性结构体¶
stat系列函数获取的文件属性,都会存储在struct stat结构体中,该结构体定义在<sys/stat.h>头文件中, 包含了文件的全部核心属性,是解析文件属性的基础,其核心成员及含义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | struct stat {
dev_t st_dev; // 文件所在设备的设备号
ino_t st_ino; // 文件的索引节点号(inode)
mode_t st_mode; // 文件类型 + 文件访问权限
nlink_t st_nlink; // 文件的硬链接数
uid_t st_uid; // 文件所有者的用户ID(UID)
gid_t st_gid; // 文件所属用户组的组ID(GID)
dev_t st_rdev; // 设备文件的设备号
off_t st_size; // 文件大小,单位为字节
blksize_t st_blksize; // 文件系统的块大小,I/O操作的最优缓冲区大小
blkcnt_t st_blocks; // 文件占用的磁盘块数(每块512字节)
time_t st_atime; // 文件最后访问时间(access)
time_t st_mtime; // 文件最后修改时间(modify)
time_t st_ctime; // 文件最后状态改变时间(change)
};
|
st_atime、st_mtime、st_ctime均为time_t类型的时间戳,存储的是从1970年1月1日0时(Unix纪元)到对应时间的秒数; st_mode是16位整型数,高4位标识文件类型,低9位标识文件访问权限,需拆分解析。
4.2. 文件属性获取核心函数¶
Linux提供stat、fstat、lstat三个系统调用获取文件属性,三者功能相近但适用场景不同, 其中stat和fstat是本章核心,lstat用于区分软链接文件。
4.2.1. stat函数¶
stat函数用于根据传入的文件路径,获取对应文件的属性信息,填充至statbuf指向的结构体中, 若路径指向软链接文件,会自动追踪软链接,获取源文件的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // 所需头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
// 函数原型
int stat(const char *pathname, struct stat *statbuf);
// 参数:
// pathname:待获取属性的文件路径字符串,const char*类型,需保证路径合法
// statbuf:struct stat类型的指针,用于存储获取到的文件属性,需提前定义
// 返回值说明
// 成功:返回0,文件属性已填充至statbuf结构体
// 失败:返回-1,同时设置全局errno变量
|
4.2.2. fstat函数¶
fstat函数用于根据已打开文件的文件描述符,获取对应文件的属性信息,填充至statbuf指向的结构体中, 无需文件路径,仅依赖文件描述符,适用于已通过open/fopen打开文件的场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // 所需头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
// 函数原型
int fstat(int fd, struct stat *statbuf);
// 参数:
// fd:已打开文件的有效文件描述符,int类型,需提前通过open等函数获取
// statbuf:struct stat类型的指针,用于存储获取到的文件属性
// 返回值说明
// 成功:返回0
// 失败:返回-1,设置全局errno变量
|
4.2.3. lstat函数¶
lstat函数与stat函数功能类似,但不会追踪软链接文件,直接获取软链接本身的属性, 适用于需要判断文件是否为软链接、获取软链接自身属性的场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 所需头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
// 函数原型
int lstat(const char *pathname, struct stat *statbuf);
// 参数与返回值
// pathname:待获取属性的文件路径字符串,const char*类型,需保证路径合法
// statbuf:struct stat类型的指针,用于存储获取到的文件属性,需提前定义
// 返回值说明
// 成功:返回0,文件属性已填充至statbuf结构体
// 失败:返回-1,同时设置全局errno变量
|
4.2.4. 三大函数使用场景对比¶
函数 |
操作依据 |
软链接处理 |
适用场景 |
|---|---|---|---|
stat |
文件路径 |
追踪软链接,获取源文件属性 |
仅需文件属性,无需区分是否为软链接的常规场景 |
fstat |
文件描述符 |
追踪软链接,获取源文件属性 |
已打开文件,需获取属性,规避路径风险 |
lstat |
文件路径 |
不追踪软链接,获取软链接自身属性 |
判断文件是否为软链接、操作软链接本身 |
4.3. 文件属性解析与获取¶
4.3.1. 文件大小获取¶
文件大小是最常用的文件属性,直接存储在struct stat结构体的st_size成员中,单位为字节,无需复杂解析,直接读取即可。
普通文件的st_size为实际数据大小;设备文件、管道文件的st_size无实际意义;空洞文件的st_size为逻辑大小,大于实际占用磁盘大小。
使用方式:调用stat/fstat函数成功后,statbuf.st_size即为文件大小,off_t类型,可直接打印或参与计算。
4.3.2. 文件权限解析¶
文件权限存储在st_mode成员的低9位,分为三类权限:文件所有者(User)权限、所属用户组(Group)权限、其他用户(Others)权限, 每类权限占3位,分别对应读(r)、写(w)、执行(x)权限,对应的宏定义如下:
所有者权限:S_IRUSR(读)、S_IWUSR(写)、S_IXUSR(执行)
用户组权限:S_IRGRP(读)、S_IWGRP(写)、S_IXGRP(执行)
其他用户权限:S_IROTH(读)、S_IWOTH(写)、S_IXOTH(执行)
解析方法:通过按位与操作,提取st_mode低9位,判断对应权限位是否置1,即可确定文件的访问权限, 也可直接将低9位转为八进制数,直观展示权限,如0644、0755。
4.3.3. 文件时间戳解析¶
struct stat包含三个核心时间戳,均为time_t类型的秒数时间戳,需结合<time.h>头文件的函数转为可读时间格式:
st_atime:最后访问时间,文件被读取、执行时更新
st_mtime:最后修改时间,文件内容被修改时更新,是最常用的时间戳
st_ctime:最后状态改变时间,文件内容、权限、属主等属性改变时更新
格式化方法:调用ctime(&时间戳)函数,可直接将time_t类型时间戳转为形如“Wed Mar 11 15:30:00 2026”的可读字符串; 也可调用localtime函数转为struct tm结构体,自定义时间格式。
4.3.4. 文件类型判断¶
文件类型存储在st_mode成员的高4位,通过专用宏定义判断,无需手动解析,常用判断宏如下:
S_ISREG(st_mode):是否为普通文件
S_ISDIR(st_mode):是否为目录文件
S_ISLNK(st_mode):是否为软链接文件
S_ISCHR(st_mode):是否为字符设备文件
S_ISBLK(st_mode):是否为块设备文件
S_ISFIFO(st_mode):是否为管道文件
S_ISSOCK(st_mode):是否为套接字文件
4.3.5. 文件属主解析¶
文件的UID(st_uid)和GID(st_gid)仅为数字标识,需结合<pwd.h>、<grp.h>头文件的函数转为用户名和组名:
getpwuid(st_uid):传入UID,返回struct passwd结构体,pw_name成员为用户名
getgrgid(st_gid):传入GID,返回struct group结构体,gr_name成员为组名
4.4. 文件属性操作示例¶
本示例整合stat、fstat函数用法,实现文件属性全解析, 涵盖文件大小、权限、时间戳、文件类型、属主信息等全部核心属性,附带完整错误处理。
4.4.1. 程序源码¶
本节我们通过一个实验,来讲解如何实现文件属性操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | #include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
void print_file_attr(struct stat *statbuf);
int main(int argc, char *argv[])
{
// 校验参数
if(argc != 2){
fprintf(stderr, "用法:%s <文件路径>\n", argv[0]);
return -1;
}
struct stat statbuf;
// 方式1:通过stat函数,路径获取文件属性
if(stat(argv[1], &statbuf) == -1){
perror("stat获取文件属性失败");
return -1;
}
printf("========== 通过stat函数获取文件属性 ==========\n");
print_file_attr(&statbuf);
// 方式2:通过fstat函数,文件描述符获取属性
int fd = open(argv[1], O_RDONLY);
if(fd == -1){
perror("open文件失败");
return -1;
}
struct stat fstat_buf;
if(fstat(fd, &fstat_buf) == -1){
perror("fstat获取文件属性失败");
close(fd);
return -1;
}
printf("\n========== 通过fstat函数获取文件属性 ==========\n");
print_file_attr(&fstat_buf);
// 关闭文件描述符
close(fd);
return 0;
}
// 解析并打印文件所有核心属性
void print_file_attr(struct stat *statbuf)
{
// 1. 打印文件索引节点号
printf("文件inode号:%ld\n", statbuf->st_ino);
// 2. 打印文件类型
printf("文件类型:");
if(S_ISREG(statbuf->st_mode)) printf("普通文件\n");
else if(S_ISDIR(statbuf->st_mode)) printf("目录文件\n");
else if(S_ISLNK(statbuf->st_mode)) printf("软链接文件\n");
else if(S_ISCHR(statbuf->st_mode)) printf("字符设备文件\n");
else if(S_ISBLK(statbuf->st_mode)) printf("块设备文件\n");
else if(S_ISFIFO(statbuf->st_mode)) printf("管道文件\n");
else if(S_ISSOCK(statbuf->st_mode)) printf("套接字文件\n");
else printf("未知文件类型\n");
// 3. 打印文件权限,八进制格式
printf("文件权限:%03o\n", statbuf->st_mode & 0777);
// 4. 打印文件大小
printf("文件大小:%ld 字节\n", statbuf->st_size);
// 5. 打印文件硬链接数
printf("硬链接数:%ld\n", statbuf->st_nlink);
// 6. 打印文件属主与属组
struct passwd *pw = getpwuid(statbuf->st_uid);
struct group *gr = getgrgid(statbuf->st_gid);
printf("文件所有者:%s\n", pw->pw_name);
printf("文件所属组:%s\n", gr->gr_name);
// 7. 打印文件时间戳
printf("最后访问时间:%s", ctime(&statbuf->st_atime));
printf("最后修改时间:%s", ctime(&statbuf->st_mtime));
printf("最后状态改变时间:%s", ctime(&statbuf->st_ctime));
}
|
以上程序先后通过stat和fstat函数获取文件属性,打印文件inode号、类型、权限、大小、链接数、属主信息、三类时间戳等全部核心内容, 展示文件属性解析效果,若文件路径非法或权限不足,会通过perror打印具体错误原因。
4.4.2. Makefile说明¶
Makefile是跟工程目录匹配的,本实验仅有一个C文件,且与Makefile处于同级目录。
Makefile内容如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #定义变量
TARGET = file_attributes
#定义编译器
CC = gcc
#定义头文件的位置()
CFLAGS = -I.
#定义头文件
DEPS =
#定义目标文件
OBJS = $(TARGET).o
#目标文件
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(CFLAGS)
#*.o文件的生成规则
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
#伪目标
.PHONY: clean
#make clean清除编译结果
clean:
rm -f $(TARGET) $(TARGET).o
|
4.4.3. 编译及测试¶
使用如下步骤进行编译测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #编译程序
make
#执行程序
./file_attributes /etc/apt/sources.list
#程序输入程序
========== 通过stat函数获取文件属性 ==========
文件inode号:8668437
文件类型:普通文件
文件权限:644
文件大小:907 字节
硬链接数:1
文件所有者:root
文件所属组:root
最后访问时间:Sun Mar 8 15:47:28 2026
最后修改时间:Fri May 16 05:54:03 2025
最后状态改变时间:Fri May 16 05:54:03 2025
========== 通过fstat函数获取文件属性 ==========
文件inode号:8668437
文件类型:普通文件
文件权限:644
文件大小:907 字节
硬链接数:1
文件所有者:root
文件所属组:root
最后访问时间:Sun Mar 8 15:47:28 2026
最后修改时间:Fri May 16 05:54:03 2025
最后状态改变时间:Fri May 16 05:54:03 2025
|
4.5. 文件属性操作注意事项¶
严格校验返回值:stat/fstat函数失败均返回-1,必须做错误判断,结合errno排查原因,避免空指针访问或无效数据解析。
区分stat与lstat:操作软链接文件时,明确需求选择函数,避免误获取源文件属性导致逻辑错误。
时间戳使用规范:time_t类型时间戳为秒数,格式化时注意时区问题,ctime函数自带换行符,打印时无需额外添加。
权限解析精准性:仅提取st_mode低9位作为权限,切勿直接使用整个st_mode值,避免文件类型位干扰权限判断。
属主解析容错:getpwuid、getgrgid可能返回NULL,需增加判空处理,防止程序崩溃。
文件描述符管理:使用fstat函数时,确保文件描述符有效且未关闭,使用完毕后及时close释放资源。
特殊文件适配:目录、设备、管道等特殊文件的部分属性无实际意义,解析时需针对性过滤,避免无效逻辑。
