目录
- 一、前言
- 二、相关函数介绍
- 三、代码实现
- 附:源代码下载
一、前言
在某些应用场景下,需要读取目录(文件夹)中所有的子目录和文件的信息,本文就是通过C语言编程实现这个功能,不依赖任何第三方的库。
本文的代码主要实现的功能有:
1、读取目录中的文件信息(只读取一级目录)。
2、递归读取目录,获取目录中所有的子目录和文件路径。
3、获取文件信息,包括文件类型(目录、普通文件等)、文件大小、文件的时间属性(创建时间、修改时间、访问时间)等。
4、将 time_t 类型的时间转换为字符串。
二、相关函数介绍
1、函数 DIR *opendir(const char *name);
头文件:#include
#include
函数说明:打开一个目录,并返回指向该目录的目录流指针
参数:目录路径字符串
返回值:成功返回指向目录流的指针;失败返回NULL,并设置错误码errno
2、函数 int closedir(DIR *dirp);
头文件:#include
#include
函数说明:关闭一个目录流
参数:目录流指针
返回值:成功返回0;失败返回-1,并设置错误码errno
3、函数 struct dirent *readdir(DIR *dirp);
头文件:#include
函数说明:读一个目录,并返回目录内的文件信息
参数:目录流指针
返回值:成功返回 dirent 的结构类型;失败返回NULL,并设置错误码errno
结构体 struct dirent 详解:
struct dirent{long d_ino; /* inode number 索引节点号 */off_t d_off; /* offset to this dirent 在目录文件中的偏移 */unsigned short d_reclen;/* length of this d_name 文件名长 */unsigned char d_type; /* the type of d_name 文件类型 */char d_name [NAME_MAX+1];/* file name (null-terminated) 文件名,最长255字符 */}
4、函数 int stat(const char* path, struct stat* buf);
头文件:#include
#include
#include
函数说明:获取文件信息
参数:文件路径(名),struct stat 类型的结构体
返回值:成功返回0;失败返回-1,并设置错误码errno
结构体 struct stat 详解:
struct stat { dev_t st_dev; /* ID of device containing file */ // 文件所在设备的ID ino_t st_ino; /* inode number */ // inode节点号 mode_t st_mode; /* protection */ // 文件对应的模式,文件、目录等 nlink_t st_nlink; /* number of hard links */ // 链向此文件的连接数(硬连接) uid_t st_uid; /* user ID of owner */ // 所有者用户ID gid_t st_gid; /* group ID of owner */ // 所有者组ID dev_t st_rdev; /* device ID (if special file) */ // 设备号,针对设备文件 off_t st_size; /* total size, in bytes */ // 文件大小,单位为字节 blksize_t st_blksize; /* blocksize for filesystem I/O */ // 系统块的大小(文件内容对应的块大小) blkcnt_t st_blocks; /* number of 512B blocks allocated */ // 文件所占块数 time_t st_atime; /* time of last access */ // 最近存取时间(最近一次访问的时间) time_t st_mtime; /* time of last modification */ // 最近修改时间 time_t st_ctime; /* time of last status change */ // 文件状态改变时间(文件创建时间)};
结构体 struct stat 中的 st_mode 属性可以用来判断指定文件为目录、普通文件、链接文件等,可以通过使用相应的宏进行判断,以下列出部分常用文件的宏,以及其使用方法。
S_ISDIR(st_mode):是否为目录S_ISREG(st_mode):是否为常规文件S_ISLNK(st_mode):是否为链接文件S_ISCHR(st_mode):是否为字符设备S_ISBLK(st_mode):是否为块设备S_ISFIFO(st_mode):是否为FIFO文件S_ISSOCK(st_mode):是否为SOCKET文件
三、代码实现
头文件 readdir.h
/* * 模块功能: * 1、读取目录中的文件路径。 * 2、递归获取目录、文件的路径信息。 * 3、获取文件信息,包括文件的类型(目录、普通文件等)、文件大小、文件的时间属性(创建时间、修改时间、访问时间)等。 * 4、将time_t类型的时间转换为字符串。 * * 修改时间:2022-09-27 */#ifndef READDIR_H#define READDIR_H#ifdef __cplusplusextern "C"{#endif#include #include #include /********************* 目录文件读取 *********************/#define PATH_LENGTH 256 // 目录或文件路径长度// 目录或文件路径信息typedef struct _pathInfo { struct _pathInfo *pre; // 上一个路径信息,头节点记录最后一个节点的地址 struct _pathInfo *next; // 下一个路径信息 bool isdir; // 是否为目录 char name[PATH_LENGTH]; // 路径名称} pathInfo;// 注:头节点不存储实际的信息,只保存前后节点的索引pathInfo *init_head_node();void free_memory(pathInfo *path_info);int read_dir(const char *dir_path, pathInfo *path_info);int read_dir_ex(const char *dir_path, pathInfo *path_info);int read_file_path(const char *dir_path, pathInfo *path_info);int read_file_path_ex(const char *dir_path, pathInfo *path_info);/********************* 文件信息获取 *********************///struct stat {// dev_t st_dev; /* ID of device containing file */ // 文件所在设备的ID// ino_t st_ino; /* inode number */ // inode节点号// mode_t st_mode; /* protection */ // 文件对应的模式,文件、目录等// nlink_t st_nlink; /* number of hard links */ // 链向此文件的连接数(硬连接)// uid_t st_uid; /* user ID of owner */ // 所有者用户ID// gid_t st_gid; /* group ID of owner */ // 所有者组ID// dev_t st_rdev; /* device ID (if special file) */ // 设备号,针对设备文件// off_t st_size; /* total size, in bytes */ // 文件大小,单位为字节// blksize_t st_blksize; /* blocksize for filesystem I/O */ // 系统块的大小(文件内容对应的块大小)// blkcnt_t st_blocks; /* number of 512B blocks allocated */ // 文件所占块数// time_t st_atime; /* time of last access */ // 最近存取时间(最近一次访问的时间)// time_t st_mtime; /* time of last modification */ // 最近修改时间// time_t st_ctime; /* time of last status change */ // 文件状态改变时间(文件创建时间)//};// st_mode属性可以用来判断指定文件为目录、普通文件等,可以通过使用相应的宏进行判断,使用方法:// S_ISDIR(st_mode):是否为目录// S_ISREG(st_mode):是否为常规文件// S_ISLNK(st_mode):是否为链接文件// S_ISCHR(st_mode):是否为字符设备// S_ISBLK(st_mode):是否为块设备// S_ISFIFO(st_mode):是否为FIFO文件// S_ISSOCK(st_mode):是否为SOCKET文件int get_file_info(const char *file_path, struct stat *sta);void time_to_str(char *str, const size_t size, const time_t t);#ifdef __cplusplus}#endif#endif // READDIR_H
源文件 readdir.c
/* * 模块功能: * 1、读取目录中的文件路径。 * 2、递归获取目录、文件的路径信息。 * 3、获取文件信息,包括文件的类型(目录、普通文件等)、文件大小、文件的时间属性(创建时间、修改时间、访问时间)等。 * 4、将time_t类型的时间转换为字符串。 * * 修改时间:2022-09-27 */#include #include #include #include #include #include #include "readdir.h"// 两个整型数的最大值、最小值#define MAX(a, b) ((a) >= (b) ? (a) : (b))#define MIN(a, b) ((a) <= (b) ? (a) : (b))/********************* 目录文件读取 *********************//** * @brief init_head_node 初始化头节点 * @return 头节点指针(动态分配的堆内存空间,后续需要调用free_memory()函数释放) */pathInfo *init_head_node(){ pathInfo *head_info = (pathInfo *)malloc(sizeof(pathInfo)); if(!head_info) { return NULL; } head_info->pre = NULL; head_info->next = NULL; //printf("%p\n", head_info); return head_info;}/** * @brief free_memory 释放内存,模块函数调用动态申请的堆内存 * @param path_info 链表头节点 */void free_memory(pathInfo *path_info){ if(!path_info) { return; } pathInfo *info = path_info; pathInfo *info1; while(info) { info1 = info->next; info->pre = NULL; info->next = NULL; //printf("%s : free_memory = %p\n", __FUNCTION__, info); free(info); info = info1; }}/** * @brief read_dir 获取目录中的文件夹和文件的名字,只读取一级目录 * @param dir_path 需要读取的目录 * @param path_info 读取返回的信息头节点,头节点存储用户输入的目录信息,读取到的信息通过内部的指针next偏移读取 * @return 0:成功 -1:失败,非法参数 -2:目录打开失败 -3:获取文件信息失败 * 注:读取返回的信息是文件夹和文件的名字。 */int read_dir(const char *dir_path, pathInfo *path_info){ DIR *dir = NULL; struct dirent *entry; struct stat sta; char path[PATH_LENGTH]; pathInfo *pre = NULL, *last = NULL; int ret_val = 0; if(!dir_path || !path_info) { return -1; } dir = opendir(dir_path); if(dir == NULL) { return -2; } // 头节点存储用户输入的目录信息 path_info->pre = NULL; path_info->next = NULL; path_info->isdir = true; memset(path_info->name, 0, sizeof(path_info->name)); memcpy(path_info->name, dir_path, MIN(strlen(dir_path), PATH_LENGTH - 1)); while(1) { entry = readdir(dir); if(entry == NULL) { break; } if(strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } memset(path, 0, sizeof(path)); strcpy(path, dir_path); if(path[strlen(path) - 1] != '/') { strcat(path, "/"); } strcat(path, entry->d_name); if(stat(path, &sta) < 0) { ret_val = -3; break; } if(last) { pre = last; } last = (pathInfo *)malloc(sizeof(pathInfo)); //printf("%s : malloc_memory = %p\n", __FUNCTION__, last); if(S_ISDIR(sta.st_mode)) { last->isdir = true; } else { last->isdir = false; } last->pre = pre; last->next = NULL; memset(last->name, 0, PATH_LENGTH); memcpy(last->name, entry->d_name, MIN(strlen(entry->d_name), PATH_LENGTH - 1)); path_info->pre = last; if(pre) { pre->next = last; } else { path_info->next = last; } } closedir(dir); return ret_val;}/** * @brief read_dir_ex 获取目录中的文件夹和文件的路径,只读取一级目录 * @param dir_path 需要读取的目录 * @param path_info 读取返回的信息头节点,头节点存储用户输入的目录信息,读取到的信息通过内部的指针next偏移读取 * @return 0:成功 -1:失败,非法参数 -2:目录打开失败 -3:获取文件信息失败 * 注:读取返回的信息是文件夹和文件的完整路径。 */int read_dir_ex(const char *dir_path, pathInfo *path_info){ DIR *dir = NULL; struct dirent *entry; struct stat sta; char path[PATH_LENGTH]; pathInfo *pre = NULL, *last = NULL; int ret_val = 0; if(!dir_path || !path_info) { return -1; } dir = opendir(dir_path); if(dir == NULL) { return -2; } // 头节点存储用户输入的目录信息 path_info->pre = NULL; path_info->next = NULL; path_info->isdir = true; memset(path_info->name, 0, sizeof(path_info->name)); memcpy(path_info->name, dir_path, MIN(strlen(dir_path), PATH_LENGTH - 1)); while(1) { entry = readdir(dir); if(entry == NULL) { break; } if(strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } memset(path, 0, sizeof(path)); strcpy(path, dir_path); if(path[strlen(path) - 1] != '/') { strcat(path, "/"); } strcat(path, entry->d_name); if(stat(path, &sta) < 0) { ret_val = -3; break; } if(last) { pre = last; } last = (pathInfo *)malloc(sizeof(pathInfo)); //printf("%s : malloc_memory = %p\n", __FUNCTION__, last); if(S_ISDIR(sta.st_mode)) { last->isdir = true; } else { last->isdir = false; } last->pre = pre; last->next = NULL; memset(last->name, 0, PATH_LENGTH); memcpy(last->name, path, MIN(strlen(path), PATH_LENGTH - 1)); path_info->pre = last; if(pre) { pre->next = last; } else { path_info->next = last; } } closedir(dir); return ret_val;}/** * @brief read_file_path 获取目录中所有的文件路径,递归读取 * @param dir_path 需要读取的目录 * @param path_info 读取返回的信息头节点,头节点存储用户输入的目录信息,读取到的信息通过内部的指针next偏移读取 * @return 0:成功 -1:失败,非法参数 -2:目录打开失败 -3:获取文件信息失败 * 注:读取返回的只是文件的路径,不包含空文件夹 */int read_file_path(const char *dir_path, pathInfo *path_info){ int ret_val; pathInfo info, *info2, info3, *info4; pathInfo *pre = NULL, *last = NULL; if(!dir_path || !path_info) { return -1; } // 头节点存储用户输入的目录信息 path_info->pre = NULL; path_info->next = NULL; path_info->isdir = true; memset(path_info->name, 0, sizeof(path_info->name)); memcpy(path_info->name, dir_path, MIN(strlen(dir_path), PATH_LENGTH - 1)); ret_val = read_dir_ex(dir_path, &info); if(ret_val == 0) { info2 = info.next; while(info2) { if(last) { pre = last; // 后续继续追加链表数据,在这里记录一下最后一个节点 } if(info2->isdir) { ret_val = read_file_path(info2->name, &info3); if(ret_val != 0) { break; } if(info3.next) { if(path_info->next) { // 头节点不为空,找到最后一个节点,将读取到的链表追加到最后一个节点后面 info3.next->pre = path_info->pre; path_info->pre->next = info3.next; path_info->pre = info3.pre; } else { // 头节点为空,直接将读取到的链表添加到头节点 path_info->pre = info3.pre; path_info->next = info3.next; } last = path_info->pre; } } else { if(last) { info2->pre = last; last = info2; } else { last = info2; } path_info->pre = last; if(pre) { pre->next = last; } else { path_info->next = last; } } info4 = info2; info2 = info2->next; // 释放目录信息结构体的内存 if(info4->isdir) { info4->pre = NULL; info4->next = NULL; //printf("%s : free_memory = %p\n", __FUNCTION__, info4); free(info4); } } } return ret_val;}/** * @brief read_file_path_ex 获取目录中所有的文件路径(包含最深层的空文件夹),递归读取 * @param dir_path 需要读取的目录 * @param path_info 读取返回的信息头节点,头节点存储用户输入的目录信息,读取到的信息通过内部的指针next偏移读取 * @return 0:成功 -1:失败,非法参数 -2:目录打开失败 -3:获取文件信息失败 * 注:读取返回的只是文件的路径,包含空文件夹 */int read_file_path_ex(const char *dir_path, pathInfo *path_info){ int ret_val; pathInfo info, *info2, info3, *info4; pathInfo *pre = NULL, *last = NULL; if(!dir_path || !path_info) { return -1; } // 头节点存储用户输入的目录信息 path_info->pre = NULL; path_info->next = NULL; path_info->isdir = true; memset(path_info->name, 0, sizeof(path_info->name)); memcpy(path_info->name, dir_path, MIN(strlen(dir_path), PATH_LENGTH - 1)); ret_val = read_dir_ex(dir_path, &info); if(ret_val == 0) { info2 = info.next; while(info2) { if(last) { pre = last; // 后续继续追加链表数据,在这里记录一下最后一个节点 } if(info2->isdir) { ret_val = read_file_path_ex(info2->name, &info3); if(ret_val != 0) { break; } if(info3.next) { if(path_info->next) { // 头节点不为空,找到最后一个节点,将读取到的链表追加到最后一个节点后面 info3.next->pre = path_info->pre; path_info->pre->next = info3.next; path_info->pre = info3.pre; } else { // 头节点为空,直接将读取到的链表添加到头节点 path_info->pre = info3.pre; path_info->next = info3.next; } last = path_info->pre; } else { // 空文件夹 info4 = info2; info2 = info2->next; if(path_info->next) { // 头节点不为空,找到最后一个节点,将读取到的链表追加到最后一个节点后面 info4->pre = path_info->pre; info4->next = NULL; path_info->pre->next = info4; path_info->pre = info4; } else { // 头节点为空,直接将读取到的链表添加到头节点 path_info->pre = info4; path_info->next = info4; } last = path_info->pre; continue; // 跳过下面的操作,继续处理下一个节点 } } else { if(last) { info2->pre = last; last = info2; } else { last = info2; } path_info->pre = last; if(pre) { pre->next = last; } else { path_info->next = last; } } info4 = info2; info2 = info2->next; // 释放目录信息结构体的内存 if(info4->isdir) { info4->pre = NULL; info4->next = NULL; //printf("%s : free_memory = %p\n", __FUNCTION__, info4); free(info4); } } } return ret_val;}/********************* 文件信息获取 *********************//** * @brief get_file_info 获取文件信息 * @param file_path 文件路径 * @param sta 获取返回的文件信息 * @return 0:成功 -1:失败,非法参数 -2:获取文件信息失败 */int get_file_info(const char *file_path, struct stat *sta){ if(!file_path || !sta) { return -1; } if(stat(file_path, sta) != 0) { return -2; } return 0;}/** * @brief time_to_str time_t时间转换为字符串 * @param str 字符串缓冲区,存放转换输出的时间字符串 * @param size 字符串缓冲区大小 * @param t time_t时间 */void time_to_str(char *str, const size_t size, const time_t t){ struct tm *tminfo = localtime(&t); strftime(str, size, "%Y-%m-%d %H:%M:%S", tminfo);}
使用示例 main.c
#include #include "ReadDir/readdir.h"int main(int argc, char *argv[]){ // 获取目录中所有的文件路径 const char *dir_path = "111"; pathInfo *path_info = init_head_node(); read_dir_ex(dir_path, path_info); printf("==========================\n"); pathInfo *info = path_info->next; printf("head: %p, %p\n", path_info->pre, path_info->next); while(info) { printf("%p, %p, %p, %d, %s\n", info, info->pre, info->next, info->isdir, info->name); info = info->next; } free_memory(path_info); // 释放内存空间,一定要加上 printf("==========================\n"); pathInfo *path_info2 = init_head_node(); read_file_path_ex(dir_path, path_info2); struct stat sta; char tstr[32], tstr2[32], tstr3[32]; pathInfo *info2 = path_info2->next; while(info2) { // 获取文件信息 get_file_info(info2->name, &sta); printf("%d, %s\n", info2->isdir, info2->name); time_to_str(tstr, sizeof(tstr), sta.st_ctime); time_to_str(tstr2, sizeof(tstr2), sta.st_mtime); time_to_str(tstr3, sizeof(tstr3), sta.st_atime); printf("%ld, %ld, %ld, %s, %s, %s\n", sta.st_ctime, sta.st_mtime, sta.st_atime, tstr, tstr2, tstr3); info2 = info2->next; } printf("==========================\n"); free_memory(path_info2); return 0;}
注意: 代码中函数获取返回的数据保存的是链表的结构,通过指向前一个或后一个节点的指针循环遍历取出每一个节点的数据。另外,链表节点的存储空间是通过 malloc() 函数动态申请的堆内存空间,使用完后必须要调用相应的函数释放内存,否则会造成内存泄漏。
附:源代码下载
C语言读取目录和文件信息.zip