文件属性获取与目录IO操作详解

文件属性获取与目录IO操作详解

一、文件属性获取函数

1.1 函数原型及区别

在Linux系统中,我们可以使用以下三个函数来获取文件的属性信息:

c 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int stat(const char *path, struct stat *buf);    // 通过文件名获取
int fstat(int fd, struct stat *buf);             // 通过文件描述符获取  
int lstat(const char *path, struct stat *buf);   // 获取链接文件本身属性

三个函数的区别对比表:

函数 参数类型 特点 适用场景
stat() 文件路径字符串 会追踪符号链接 获取符号链接指向文件的属性
fstat() 文件描述符 通过已打开的文件获取属性 已打开文件的属性获取
lstat() 文件路径字符串 不追踪符号链接 获取符号链接文件本身的属性

1.2 struct stat 结构体详解

struct stat 结构体包含了文件的完整属性信息:

c 复制代码
struct stat {
    dev_t     st_dev;      // 文件所在设备的ID
    ino_t     st_ino;      // inode编号(文件系统内唯一标识)
    mode_t    st_mode;     // 文件类型和权限
    nlink_t   st_nlink;    // 硬链接数量
    uid_t     st_uid;      // 文件所有者的用户ID
    gid_t     st_gid;      // 文件所有者的组ID
    dev_t     st_rdev;     // 特殊设备文件的设备ID
    off_t     st_size;     // 文件大小(字节),特殊文件为0
    blksize_t st_blksize;  // 文件系统I/O的块大小
    blkcnt_t  st_blocks;   // 分配的512字节块数量
    time_t    st_atime;    // 最后访问时间
    time_t    st_mtime;    // 最后修改时间
    time_t    st_ctime;    // 最后状态改变时间
};

时间字段说明表:

时间字段 含义 触发条件
st_atime 最后访问时间 读取文件内容时更新
st_mtime 最后修改时间 修改文件内容时更新
st_ctime 最后状态改变时间 修改文件属性(如权限、所有者)时更新

1.3 文件类型判断宏

通过 st_mode 字段可以判断文件类型:

c 复制代码
S_ISREG(m)   // 是否为普通文件
S_ISDIR(m)   // 是否为目录文件
S_ISCHR(m)   // 是否为字符设备文件
S_ISBLK(m)   // 是否为块设备文件
S_ISFIFO(m)  // 是否为管道文件
S_ISLNK(m)   // 是否为符号链接文件
S_ISSOCK(m)  // 是否为套接字文件

文件类型判断示例表:

返回值 文件类型 描述
S_ISREG(st_mode) 1 普通文件 常规数据文件
S_ISDIR(st_mode) 1 目录文件 包含其他文件的目录
S_ISCHR(st_mode) 1 字符设备 如终端设备
S_ISBLK(st_mode) 1 块设备 如磁盘分区
S_ISFIFO(st_mode) 1 管道文件 FIFO特殊文件
S_ISLNK(st_mode) 1 符号链接 指向其他文件的链接
S_ISSOCK(st_mode) 1 套接字文件 用于进程间通信

1.4 代码示例

示例1:获取文件大小
c 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char** argv)
{
    if(argc < 2){
        printf("请先传递文件名\n");
        return -1;
    }
    
    struct stat info;
    if(stat(argv[1], &info) == 0){
        printf("文件大小: %ld 字节\n", info.st_size);
        printf("文件占用的磁盘块数: %ld (每块512字节)\n", info.st_blocks);
        printf("实际占用空间: %ld 字节\n", info.st_blocks * 512);
    } else {
        perror("获取文件信息失败");
    }
    return 0;
}
示例2:判断文件类型
c 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char** argv)
{
    if(argc < 2){
        printf("请先传递文件名\n");
        return -1;
    }
    
    struct stat info;
    if(stat(argv[1], &info) != 0){
        perror("获取文件信息失败");
        return -1;
    }
    
    printf("文件信息:\n");
    printf("- 权限模式: %o\n", info.st_mode & 0777);
    
    if(S_ISREG(info.st_mode)){
        printf("- 类型: 普通文件\n");
        printf("- 大小: %ld 字节\n", info.st_size);
    }
    else if(S_ISDIR(info.st_mode)){
        printf("- 类型: 目录文件\n");
    }
    else if(S_ISCHR(info.st_mode)){
        printf("- 类型: 字符设备文件\n");
    }
    else if(S_ISBLK(info.st_mode)){
        printf("- 类型: 块设备文件\n");
    }
    else if(S_ISFIFO(info.st_mode)){
        printf("- 类型: 管道文件\n");
    }
    else if(S_ISLNK(info.st_mode)){
        printf("- 类型: 符号链接文件\n");
    }
    else if(S_ISSOCK(info.st_mode)){
        printf("- 类型: 套接字文件\n");
    }
    else{
        printf("- 类型: 未知文件类型\n");
    }
    
    return 0;
}
练习:在普通文件末尾追加内容
c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    struct stat buf = {0};
    int ret = 0;
    FILE *fp = NULL;
    
    if (argc < 2)
    {
        printf("参数太少,请指定文件名\n");
        return -1;
    }
    
    // 使用lstat避免符号链接追踪
    ret = lstat(argv[1], &buf);
    if (ret < 0)
    {
        printf("获取文件属性失败\n");
        return -1;
    }
    
    if (S_ISREG(buf.st_mode) != 1)
    {
        printf("该文件不是普通文件,无法追加内容\n");
        return -1;
    }
    
    printf("该文件是普通文件,大小为 %ld 字节\n", buf.st_size);
    
    fp = fopen(argv[1], "a");
    if (NULL == fp)
    {
        perror("打开文件失败");
        return -1;
    }
    
    fwrite("hehe", 4, 1, fp);
    fclose(fp);
    
    printf("成功在文件末尾追加 'hehe'\n");
    return 0;
}

二、目录IO操作

2.1 学习目录IO的意义

文件系统层次结构示意图:

复制代码
根目录 /
├── home/
│   ├── user1/
│   │   ├── document.txt
│   │   └── photo.jpg
│   └── user2/
├── etc/
│   └── config.conf
└── var/
    └── log/

目录是组织和管理文件的容器,通过目录IO可以:

  • 遍历目录内容
  • 创建/删除目录
  • 管理目录结构
  • 实现文件搜索功能

2.2 访问文件与访问目录的区别

文件系统存储结构对比表:

组件 存储内容 访问方式
文件 实际数据内容 读写文件内容
目录 文件名和inode号的映射表 遍历目录项
inode 文件元数据(属性) 通过系统调用获取

Linux目录结构示意图:

复制代码
目录项表 (dentry)
+----------------+-----------------+
| 文件名         | inode编号       |
+----------------+-----------------+
| "file1.txt"    | 12345           |
| "file2.c"      | 12346           |
| "subdir"       | 12347           |
+----------------+-----------------+
        ↓
inode表 (存储文件属性)
+---------+-----------------------+
| inode#  | struct stat 信息      |
+---------+-----------------------+
| 12345   | 大小、权限、时间等     |
| 12346   | 大小、权限、时间等     |
| 12347   | 大小、权限、时间等     |
+---------+-----------------------+

2.3 opendir() - 打开目录

c 复制代码
#include <sys/types.h>
#include <dirent.h>

DIR *opendir(const char *name);

参数说明:

  • name:要打开的目录路径

返回值:

  • 成功:目录流指针 (DIR *)
  • 失败:NULL

验证opendir是否改变当前目录:

c 复制代码
#include <sys/types.h>
#include <dirent.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    if(argc < 2){
        printf("请指定目录路径\n");
        return -1;
    }
    
    printf("打开目录前的当前目录: ");
    system("pwd");

    DIR *dirfp = opendir(argv[1]);
    if(dirfp == NULL)
    {
        perror("opendir error");
        return -1;
    }
    
    printf("打开目录后的当前目录: ");
    system("pwd");
    
    closedir(dirfp);
    return 0;
}

结论: opendir() 只打开目录进行读取,不会改变程序的当前工作目录。

2.4 chdir() - 切换工作目录

c 复制代码
#include <unistd.h>
int chdir(const char *path);

参数:

  • path:目标路径

返回值:

  • 成功:0
  • 失败:-1
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>

int main()
{
    FILE *fp = NULL;
    char buf[1024] = {0};
    
    printf("切换前的当前目录: ");
    system("pwd");
    
    if (chdir("/home/superman") < 0)
    {
        perror("切换路径失败");
        return -1;
    }
    
    printf("切换后的当前目录: ");
    system("pwd");

    // 现在打开的是 /home/superman/1.txt
    fp = fopen("./1.txt", "r");
    if (NULL == fp)
    {
        perror("打开文件失败");
        return -1;
    }
    
    fread(buf, 1024, 1, fp);
    printf("文件内容: %s\n", buf);
    fclose(fp);
    
    return 0;
}

2.5 readdir() - 读取目录内容

c 复制代码
#include <dirent.h>
struct dirent *readdir(DIR *dirp);

struct dirent 结构体:

c 复制代码
struct dirent {
    ino_t d_ino;           // 文件索引号
    off_t d_off;           // 目录项偏移量
    unsigned short d_reclen; // 目录项大小
    unsigned char d_type;    // 文件类型
    char d_name[256];       // 文件名
};

d_type 文件类型常量表:

常量 文件类型 描述
DT_REG 8 普通文件 常规数据文件
DT_DIR 4 目录 文件夹
DT_LNK 10 符号链接 软链接文件
DT_CHR 2 字符设备 终端等字符设备
DT_BLK 6 块设备 磁盘等块设备
DT_FIFO 1 管道 FIFO特殊文件
DT_SOCK 12 套接字 进程间通信
DT_UNKNOWN 0 未知类型 无法确定类型

2.6 closedir() - 关闭目录

c 复制代码
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);

2.7 完整示例:遍历目录内容

c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>

const char* get_file_type(unsigned char d_type) {
    switch(d_type) {
        case DT_REG: return "普通文件";
        case DT_DIR: return "目录";
        case DT_LNK: return "符号链接";
        case DT_CHR: return "字符设备";
        case DT_BLK: return "块设备";
        case DT_FIFO: return "管道";
        case DT_SOCK: return "套接字";
        default: return "未知类型";
    }
}

int main(int argc, char* argv[])
{
    const char* path = ".";
    if(argc > 1) {
        path = argv[1];
    }
    
    struct dirent *entry = NULL;
    DIR *dir_p = opendir(path);
    
    if (NULL == dir_p) {
        perror("打开目录失败");
        return -1;
    }
    
    printf("目录 '%s' 的内容:\n", path);
    printf("%-15s %-10s %-15s %s\n", "索引号", "类型", "大小", "文件名");
    printf("------------------------------------------------\n");
    
    while (1) {
        entry = readdir(dir_p);
        if (NULL == entry) {
            break;
        }
        
        printf("%-15lu %-10s %-15d %s\n",
               entry->d_ino,
               get_file_type(entry->d_type),
               entry->d_reclen,
               entry->d_name);
    }
    
    closedir(dir_p);
    return 0;
}

目录遍历流程图:

复制代码
开始
  ↓
opendir()打开目录
  ↓
readdir()读取第一个目录项
  ↓
┌─→ 是否为NULL? ──→ 是 ──→ closedir()关闭目录 ──→ 结束
│       否
│   处理当前目录项信息
│        ↓
└── readdir()读取下一个目录项

2.8 进阶示例:递归遍历目录树

c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>

void list_directory(const char* path, int depth) {
    DIR *dir;
    struct dirent *entry;
    struct stat statbuf;
    char fullpath[1024];
    
    if ((dir = opendir(path)) == NULL) {
        return;
    }
    
    while ((entry = readdir(dir)) != NULL) {
        // 跳过 . 和 .. 目录
        if (strcmp(entry->d_name, ".") == 0 || 
            strcmp(entry->d_name, "..") == 0) {
            continue;
        }
        
        // 构建完整路径
        snprintf(fullpath, sizeof(fullpath), "%s/%s", path, entry->d_name);
        
        // 获取文件信息
        if (lstat(fullpath, &statbuf) == -1) {
            continue;
        }
        
        // 缩进显示层次结构
        for (int i = 0; i < depth; i++) {
            printf("  ");
        }
        
        if (S_ISDIR(statbuf.st_mode)) {
            printf("📁 %s/\n", entry->d_name);
            // 递归遍历子目录
            list_directory(fullpath, depth + 1);
        } else if (S_ISLNK(statbuf.st_mode)) {
            printf("🔗 %s -> 链接文件\n", entry->d_name);
        } else if (S_ISREG(statbuf.st_mode)) {
            printf("📄 %s (%ld 字节)\n", entry->d_name, statbuf.st_size);
        } else {
            printf("❓ %s\n", entry->d_name);
        }
    }
    
    closedir(dir);
}

int main(int argc, char* argv[]) {
    const char* path = ".";
    if (argc > 1) {
        path = argv[1];
    }
    
    printf("目录树: %s\n", path);
    printf("====================\n");
    list_directory(path, 0);
    
    return 0;
}

三、总结

文件属性获取与目录IO对比表:

功能 文件操作 目录操作
打开 open(), fopen() opendir()
读取 read(), fread() readdir()
关闭 close(), fclose() closedir()
定位 lseek(), fseek() 自动顺序读取
信息获取 stat(), fstat() readdir()返回dirent

关键知识点:

  1. stat vs lstat:处理符号链接时的区别很重要
  2. 目录流:目录读取是顺序的,类似文件流但结构不同
  3. 文件类型判断 :可以通过st_mode宏或d_type字段
  4. 错误处理:所有系统调用都应检查返回值
  5. 资源释放 :打开的目录必须用closedir()关闭
相关推荐
User_芊芊君子2 小时前
【LeetCode 经典题解】:队列与栈的双向模拟——从原理到代码详解
linux·redis·leetcode
熊文豪2 小时前
搭建AI资讯早报:AiOnly全球大模型服务+N8N自动化工作流实战
linux·运维·服务器
阿猿收手吧!3 小时前
【C语言】localtime和localtime_r;strftime和strftime_l
linux·c语言·开发语言
2401_841495643 小时前
【计算机网络】计算机网络体系结构与参考模型
网络·计算机网络·ip·tcp·osi·分层结构·协议数据单元
wanhengidc3 小时前
云真机和云手机的区别
运维·服务器·游戏·智能手机·云计算
yewq-cn4 小时前
海思 SLE 芯片 Linux 烧录
linux·服务器
顾安r4 小时前
11.5 脚本 本地网站收藏(解封归来)
linux·服务器·c语言·python·bash
QT 小鲜肉4 小时前
【QT/C++】Qt网络编程进阶:TCP网络编程的基本原理和实际应用(超详细)
c语言·开发语言·网络·c++·qt·学习·tcp/ip
zzzsde4 小时前
【Linux】权限(1):初识权限与使用理解
linux·运维·服务器