【Linux 封神之路】目录操作 + 文件属性实战:C 语言 API 全解析(附目录遍历 / 权限修改案例)

38 阅读11分钟

【Linux 封神之路】目录操作 + 文件属性实战:C 语言 API 全解析(附目录遍历 / 权限修改案例)

大家好,我是专注 Linux 技术分享的小杨。上一篇给大家拆解了文件读写和时间编程,今天接着 Linux 文件系统核心技能,聚焦 “目录操作” 和 “文件属性获取”—— 这两个是嵌入式开发、服务器运维的高频需求,比如创建目录、遍历文件夹、修改文件权限、获取文件大小 / 创建时间等场景都离不开。结合《文件属性及目录操作》PDF 资料,从核心函数解析到实战案例,手把手教你用 C 语言操控 Linux 目录和文件属性,新手直接套用代码!

一、先理清:目录操作与文件属性的核心应用场景

在 Linux 开发中,目录操作和文件属性获取是 “文件系统操控” 的基础,常见应用场景包括:

  • 目录管理:创建项目目录(如mkdir -p src/include)、切换工作目录、删除空目录;
  • 文件夹遍历:批量处理文件(如遍历日志目录、批量修改后缀)、统计目录下文件个数;
  • 文件属性查询:获取文件大小、创建时间、权限、类型(普通文件 / 目录 / 设备文件);
  • 权限管理:动态修改文件 / 目录权限(如给可执行程序添加执行权限)。

下面按 “目录操作→文件属性” 的顺序,拆解每个核心函数的用法和实战代码。

二、实战 1:目录操作核心 API(创建 / 切换 / 遍历 / 删除)

Linux 目录操作的核心函数都封装在<unistd.h><dirent.h>头文件中,按 “打开→操作→关闭” 的流程使用,与文件操作逻辑一致。

1. 核心函数详解(按使用频率排序)

函数功能核心参数 / 返回值类比 Shell 命令
getcwd获取当前工作目录路径参数 1:存储路径的缓冲区;参数 2:缓冲区大小;返回值:成功返回缓冲区地址,失败返回NULLpwd
mkdir创建空目录参数 1:目录路径(如"test_dir");参数 2:目录权限(如0755);返回值:0 成功,-1 失败mkdir
chdir切换工作目录参数:目标路径(绝对 / 相对路径);返回值:0 成功,-1 失败cd
rmdir删除空目录参数:目录路径;返回值:0 成功,-1 失败(仅能删除空目录)rmdir
opendir打开目录参数:目录路径;返回值:目录描述符指针(DIR *),失败返回NULL无(打开目录准备遍历)
readdir读取目录内容参数:opendir返回的目录指针;返回值:struct dirent *(文件信息),读取完毕返回NULLls
closedir关闭目录参数:opendir返回的目录指针;返回值:0 成功,-1 失败无(关闭目录释放资源)
chmod修改文件 / 目录权限参数 1:路径;参数 2:目标权限(如0777);返回值:0 成功,-1 失败chmod

2. 实战案例 1:目录创建 + 切换 + 获取当前路径

模拟项目初始化流程:创建project/srcproject/include目录,切换到该目录,打印当前路径:

c

运行

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

int main() {
    // 1. 创建多级目录(需先创建project,再创建子目录)
    if (mkdir("project", 0755) == -1) {
        perror("mkdir project failed");
        // 目录已存在时不影响后续操作,继续执行
    }
    if (mkdir("project/src", 0755) == -1) {
        perror("mkdir project/src failed");
    }
    if (mkdir("project/include", 0755) == -1) {
        perror("mkdir project/include failed");
    }

    // 2. 切换到project目录
    if (chdir("project") == -1) {
        perror("chdir failed");
        return 1;
    }

    // 3. 获取当前工作目录并打印
    char buf[1024];
    if (getcwd(buf, sizeof(buf)) == NULL) {
        perror("getcwd failed");
        return 1;
    }
    printf("当前工作目录:%s\n", buf); // 输出:xxx/project

    return 0;
}

3. 实战案例 2:目录遍历(统计目录下文件个数)

遍历project目录,打印所有文件 / 目录名称,统计普通文件和目录的个数:

c

运行

#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>

int main() {
    // 1. 打开目录
    DIR *dir = opendir("project");
    if (dir == NULL) {
        perror("opendir failed");
        return 1;
    }

    // 2. 读取目录内容(循环读取所有文件)
    struct dirent *entry;
    int file_count = 0;  // 普通文件计数
    int dir_count = 0;   // 目录计数

    printf("project目录下的内容:\n");
    while ((entry = readdir(dir)) != NULL) {
        // 跳过当前目录(.)和上级目录(..)
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }

        // 打印文件名和文件类型
        printf("名称:%s\t", entry->d_name);
        switch (entry->d_type) {
            case DT_REG:  // 普通文件
                printf("类型:普通文件\n");
                file_count++;
                break;
            case DT_DIR:  // 目录文件
                printf("类型:目录\n");
                dir_count++;
                break;
            case DT_LNK:  // 链接文件
                printf("类型:链接文件\n");
                break;
            default:
                printf("类型:其他\n");
                break;
        }
    }

    // 3. 关闭目录
    closedir(dir);

    // 4. 打印统计结果
    printf("\n统计:普通文件%d个,目录%d个\n", file_count, dir_count);
    return 0;
}

4. 关键注意事项

  • readdir返回的struct dirent结构体中,d_type字段直接标识文件类型(无需额外判断),常用值:DT_REG(普通文件)、DT_DIR(目录)、DT_LNK(链接文件);
  • rmdir仅能删除空目录,若目录非空,需先删除目录内所有文件 / 子目录,再删除该目录;
  • 创建多级目录时(如a/b/c),需按层级创建(先创建a,再创建a/b,最后创建a/b/c),mkdir不支持直接创建多级目录。

三、实战 2:文件属性获取与解析(大小 / 权限 / 时间)

文件属性包含文件大小、权限、创建时间、所有者等信息,核心通过stat系列函数获取,配合struct stat结构体解析,是日志统计、文件管理的核心技能。

1. 核心函数与结构体详解

(1)3 个核心获取函数(按需选择)
函数功能差异适用场景
stat获取文件属性,不跟随符号链接普通文件 / 目录属性查询
lstat获取文件属性,跟随符号链接(返回链接文件本身的属性)符号链接文件属性查询
fstat通过文件描述符(open返回值)获取属性已打开文件的属性查询(避免重复路径解析)
(2)核心结构体struct stat关键字段
字段含义实用场景
st_size文件大小(字节)统计文件占用空间、判断文件是否为空
st_mode文件类型 + 权限判断文件类型、解析文件权限(如rw-r--r--
st_atime最后访问时间(如read操作)日志统计:文件最后被访问时间
st_mtime最后修改时间(文件内容修改)版本管理:判断文件是否被修改
st_ctime最后状态修改时间(权限 / 所有者修改)权限变更审计
st_uid/st_gid文件所有者 ID / 组 ID权限控制:判断当前用户是否有权操作文件

2. 实战案例 1:解析文件属性(大小 / 权限 / 时间)

获取project/src目录下某文件的属性,解析并打印文件大小、权限、创建时间:

c

运行

#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <string.h>

// 解析文件权限(将st_mode转换为字符串格式,如rw-r--r--)
void get_file_permission(mode_t mode, char *perm) {
    // 初始化权限字符串(10个字符:-rwxrwxrwx)
    memset(perm, '-', 10);
    perm[10] = '\0';

    // 判断文件类型(第一个字符)
    if (S_ISREG(mode)) perm[0] = '-';    // 普通文件
    else if (S_ISDIR(mode)) perm[0] = 'd'; // 目录
    else if (S_ISLNK(mode)) perm[0] = 'l'; // 链接文件
    else if (S_ISCHR(mode)) perm[0] = 'c'; // 字符设备
    else if (S_ISBLK(mode)) perm[0] = 'b'; // 块设备

    // 所有者权限(r=4, w=2, x=1)
    if (mode & S_IRUSR) perm[1] = 'r';
    if (mode & S_IWUSR) perm[2] = 'w';
    if (mode & S_IXUSR) perm[3] = 'x';

    // 组用户权限
    if (mode & S_IRGRP) perm[4] = 'r';
    if (mode & S_IWGRP) perm[5] = 'w';
    if (mode & S_IXGRP) perm[6] = 'x';

    // 其他用户权限
    if (mode & S_IROTH) perm[7] = 'r';
    if (mode & S_IWOTH) perm[8] = 'w';
    if (mode & S_IXOTH) perm[9] = 'x';
}

int main() {
    struct stat buf;
    char perm[11]; // 存储权限字符串(如rw-r--r--)

    // 1. 获取文件属性(以project/src/test.c为例,需先创建该文件)
    if (stat("project/src/test.c", &buf) == -1) {
        perror("stat failed");
        return 1;
    }

    // 2. 解析并打印属性
    printf("文件属性解析:\n");
    printf("文件大小:%ld 字节\n", buf.st_size);

    // 解析权限
    get_file_permission(buf.st_mode, perm);
    printf("文件权限:%s\n", perm);

    // 解析时间(转换为本地时间字符串)
    printf("最后访问时间:%s", ctime(&buf.st_atime));
    printf("最后修改时间:%s", ctime(&buf.st_mtime));
    printf("创建时间:%s", ctime(&buf.st_ctime));

    return 0;
}

3. 实战案例 2:修改文件权限(chmod应用)

test.c文件权限修改为rwxr-xr-x(0755),再验证修改结果:

c

运行

#include <stdio.h>
#include <sys/stat.h>

int main() {
    // 1. 修改文件权限为0755(rwxr-xr-x)
    if (chmod("project/src/test.c", 0755) == -1) {
        perror("chmod failed");
        return 1;
    }
    printf("权限修改成功!\n");

    // 2. 验证修改结果
    struct stat buf;
    if (stat("project/src/test.c", &buf) == -1) {
        perror("stat failed");
        return 1;
    }

    // 打印修改后的权限(简化判断)
    printf("修改后权限:");
    printf((buf.st_mode & S_IRUSR) ? "r" : "-");
    printf((buf.st_mode & S_IWUSR) ? "w" : "-");
    printf((buf.st_mode & S_IXUSR) ? "x" : "-");
    printf((buf.st_mode & S_IRGRP) ? "r" : "-");
    printf((buf.st_mode & S_IWGRP) ? "w" : "-");
    printf((buf.st_mode & S_IXGRP) ? "x" : "-");
    printf((buf.st_mode & S_IROTH) ? "r" : "-");
    printf((buf.st_mode & S_IWOTH) ? "w" : "-");
    printf((buf.st_mode & S_IXOTH) ? "x" : "-");
    printf("\n");

    return 0;
}

4. 关键注意事项

  • 文件权限的八进制表示:0755对应rwxr-xr-x0644对应rw-r--r--,嵌入式开发中常用这两种权限;
  • ctime函数将时间戳转换为字符串时,会自动添加换行符,打印时无需额外加\n
  • lstatstat的区别:对符号链接文件,stat返回链接指向的原文件属性,lstat返回链接文件本身的属性,需根据需求选择。

四、综合实战:目录遍历 + 属性统计(批量文件管理)

结合目录遍历和文件属性解析,实现一个 “目录文件统计工具”:遍历指定目录,统计普通文件个数、总大小、最大文件名称:

c

运行

#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>

#define MAX_PATH 1024

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("用法:%s <目录路径>\n", argv[0]);
        return 1;
    }

    DIR *dir = opendir(argv[1]);
    if (dir == NULL) {
        perror("opendir failed");
        return 1;
    }

    struct dirent *entry;
    int file_count = 0;
    long total_size = 0;
    long max_size = 0;
    char max_file[MAX_PATH] = "";

    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }

        // 拼接文件完整路径
        char file_path[MAX_PATH];
        snprintf(file_path, sizeof(file_path), "%s/%s", argv[1], entry->d_name);

        // 获取文件属性
        struct stat buf;
        if (stat(file_path, &buf) == -1) {
            perror("stat failed");
            continue;
        }

        // 仅统计普通文件
        if (S_ISREG(buf.st_mode)) {
            file_count++;
            total_size += buf.st_size;

            // 记录最大文件
            if (buf.st_size > max_size) {
                max_size = buf.st_size;
                strcpy(max_file, entry->d_name);
            }
        }
    }

    closedir(dir);

    // 打印统计结果
    printf("目录:%s\n", argv[1]);
    printf("普通文件总数:%d\n", file_count);
    printf("文件总大小:%ld 字节(%.2f KB)\n", total_size, total_size / 1024.0);
    if (file_count > 0) {
        printf("最大文件:%s(%ld 字节)\n", max_file, max_size);
    }

    return 0;
}

编译与运行

bash

运行

# 编译
gcc dir_stat.c -o dir_stat
# 运行(统计project目录)
./dir_stat project

输出结果示例

plaintext

目录:project
普通文件总数:3
文件总大小:1200 字节(1.17 KB)
最大文件:test.c(800 字节)

五、避坑指南:常见错误解决

  1. 目录遍历时报错 “Too many open files”

    • 原因:打开目录后未调用closedir关闭,导致文件描述符泄露;
    • 解决:遍历完成后必须调用closedir(dir),释放目录描述符。
  2. stat函数报错 “No such file or directory”

    • 原因:文件路径错误(相对路径需基于当前工作目录,或使用绝对路径);
    • 解决:用getcwd确认当前工作目录,或拼接绝对路径(如/home/user/project/test.c)。
  3. 权限解析错误(如x权限未正确显示)

    • 原因:st_mode与权限掩码(如S_IXUSR)的按位与判断错误;
    • 解决:确认权限掩码使用正确(所有者用S_IRUSR/S_IWUSR/S_IXUSR,组用户用S_IRGRP等)。
  4. rmdir报错 “Directory not empty”

    • 原因:目录内有文件 / 子目录,rmdir仅能删除空目录;
    • 解决:先遍历目录删除所有文件 / 子目录,再删除该目录(可递归实现)。

六、总结:核心技能与应用场景

  1. 目录操作:

    • 基础操作(mkdir/chdir/getcwd):适合项目初始化、工作目录切换;
    • 遍历操作(opendir/readdir/closedir):适合批量文件处理、目录统计;
  2. 文件属性:

    • 属性获取(stat/lstat/fstat):适合文件管理、日志统计、权限判断;
    • 权限修改(chmod):适合动态调整文件访问权限,保障系统安全;
  3. 嵌入式适配:

    • 权限控制:嵌入式设备中文件权限需严格配置(如0755执行权限、0644只读权限),避免权限过高导致安全风险;
    • 资源释放:目录 / 文件操作后必须关闭(closedir/close),避免嵌入式设备资源耗尽。

掌握目录操作和文件属性解析,能应对 Linux 开发中 90% 的文件系统相关需求,比如日志管理、批量部署、权限审计等。下一篇博客,我会给大家整理 “Linux 多进程 / 多线程编程”,解决并发执行、资源同步等问题,敬请关注!