【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:缓冲区大小;返回值:成功返回缓冲区地址,失败返回NULL | pwd |
mkdir | 创建空目录 | 参数 1:目录路径(如"test_dir");参数 2:目录权限(如0755);返回值:0 成功,-1 失败 | mkdir |
chdir | 切换工作目录 | 参数:目标路径(绝对 / 相对路径);返回值:0 成功,-1 失败 | cd |
rmdir | 删除空目录 | 参数:目录路径;返回值:0 成功,-1 失败(仅能删除空目录) | rmdir |
opendir | 打开目录 | 参数:目录路径;返回值:目录描述符指针(DIR *),失败返回NULL | 无(打开目录准备遍历) |
readdir | 读取目录内容 | 参数:opendir返回的目录指针;返回值:struct dirent *(文件信息),读取完毕返回NULL | ls |
closedir | 关闭目录 | 参数:opendir返回的目录指针;返回值:0 成功,-1 失败 | 无(关闭目录释放资源) |
chmod | 修改文件 / 目录权限 | 参数 1:路径;参数 2:目标权限(如0777);返回值:0 成功,-1 失败 | chmod |
2. 实战案例 1:目录创建 + 切换 + 获取当前路径
模拟项目初始化流程:创建project/src和project/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-x,0644对应rw-r--r--,嵌入式开发中常用这两种权限; ctime函数将时间戳转换为字符串时,会自动添加换行符,打印时无需额外加\n;lstat与stat的区别:对符号链接文件,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 字节)
五、避坑指南:常见错误解决
-
目录遍历时报错 “Too many open files” :
- 原因:打开目录后未调用
closedir关闭,导致文件描述符泄露; - 解决:遍历完成后必须调用
closedir(dir),释放目录描述符。
- 原因:打开目录后未调用
-
stat函数报错 “No such file or directory” :- 原因:文件路径错误(相对路径需基于当前工作目录,或使用绝对路径);
- 解决:用
getcwd确认当前工作目录,或拼接绝对路径(如/home/user/project/test.c)。
-
权限解析错误(如
x权限未正确显示) :- 原因:
st_mode与权限掩码(如S_IXUSR)的按位与判断错误; - 解决:确认权限掩码使用正确(所有者用
S_IRUSR/S_IWUSR/S_IXUSR,组用户用S_IRGRP等)。
- 原因:
-
rmdir报错 “Directory not empty” :- 原因:目录内有文件 / 子目录,
rmdir仅能删除空目录; - 解决:先遍历目录删除所有文件 / 子目录,再删除该目录(可递归实现)。
- 原因:目录内有文件 / 子目录,
六、总结:核心技能与应用场景
-
目录操作:
- 基础操作(
mkdir/chdir/getcwd):适合项目初始化、工作目录切换; - 遍历操作(
opendir/readdir/closedir):适合批量文件处理、目录统计;
- 基础操作(
-
文件属性:
- 属性获取(
stat/lstat/fstat):适合文件管理、日志统计、权限判断; - 权限修改(
chmod):适合动态调整文件访问权限,保障系统安全;
- 属性获取(
-
嵌入式适配:
- 权限控制:嵌入式设备中文件权限需严格配置(如
0755执行权限、0644只读权限),避免权限过高导致安全风险; - 资源释放:目录 / 文件操作后必须关闭(
closedir/close),避免嵌入式设备资源耗尽。
- 权限控制:嵌入式设备中文件权限需严格配置(如
掌握目录操作和文件属性解析,能应对 Linux 开发中 90% 的文件系统相关需求,比如日志管理、批量部署、权限审计等。下一篇博客,我会给大家整理 “Linux 多进程 / 多线程编程”,解决并发执行、资源同步等问题,敬请关注!