前言:笔者没有抓住本科大把大把的时光认真学习编程,读研了只能遇到什么问题找什么解决方案。因此打算用博客的方式把遇到的问题和解决问题的方法记录下来,不仅有利于自己今后回顾,也算是以一种传递技术的方式感谢帮助过我的网友,以及看过的精品博文的博主。
问题背景
笔者最近在学习Caffe深度学习框架,在建立lmdb格式的数据集时,需要新建一个txt文件,在里面每行以“图片文件名 种类编号”的格式建立一个列表,用于接下来调用脚本建立lmdb数据集。而深度学习数据集往往都是成千上万量级的,笔者的数据集也是自己搜集、经人工分类的独一无二的数据集,原始数据根据类别存放在不同文件夹中。因此产生了读取文件夹中所有文件的需求。
问题描述
系统:Ubuntu 16.04
语言:C/C++
要求:读取指定文件夹下所有文件,并输出文件名。
解决方案
利用dirent库
dirent简介
dirent是Linux下的一个头文件,其位置在/usr/include/,为获取某文件夹内的目录内容提供接口
头文件引用:#include <dirent.h>
dirent结构体
该结构体用于表示文件夹下读取的文件。
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) 文件名,最长256字符 */
}
其中重要的属性有:
- d_type属性
它标示着dirent结构指向的文件的类型。
类型有:
enum
{
DT_UNKNOWN = 0, //未知类型
# define DT_UNKNOWN DT_UNKNOWN
DT_FIFO = 1, //管道
# define DT_FIFO DT_FIFO
DT_CHR = 2, //字符设备
# define DT_CHR DT_CHR
DT_DIR = 4, //目录
# define DT_DIR DT_DIR
DT_BLK = 6, //块设备
# define DT_BLK DT_BLK
DT_REG = 8, //常规文件
# define DT_REG DT_REG
DT_LNK = 10, //符号链接
# define DT_LNK DT_LNK
DT_SOCK = 12, //套接字
# define DT_SOCK DT_SOCK
DT_WHT = 14 //链接
# define DT_WHT DT_WHT
};
一般的文件即常规文件,因为直接将文件夹内所有文件都读取的话,也会将一些特殊的非常规文件读取,例如文件夹(包括正常文件夹,和 . 和 … 这两个特殊文件夹),所以常通过判断d_type属性的值来确认是否需要处理当前文件。
- d_name属性
表示dirent结构体变量指向的文件的名称,是字符串。
DIR结构体
该结构体用于指示要操作的文件夹
DIR结构体定义如下:
struct __dirstream
{
void *__fd; /* `struct hurd_fd' pointer for descriptor. */
char *__data; /* Directory block. */
int __entry_data; /* Entry number `__data' corresponds to. */
char *__ptr; /* Current pointer into the block. */
int __entry_ptr; /* Entry number `__ptr' corresponds to. */
size_t __allocation; /* Space allocated for the block. */
size_t __size; /* Total valid data in the block. */
__libc_lock_define (, __lock) /* Mutex lock for this structure. */
};
typedef struct __dirstream DIR;
这个结构体没有什么太多好讲的,主要用于接受接下来要讲的函数的返回值,指示特定的文件夹。
opendir()函数
函数原型:DIR* opendir (const char * path );
参数:要指示的文件夹名
- 在linux系统下,目录都用/符号隔开,举例:
dir = opendir("/home/nvidia/Pictures/");
- 在windows下,目录用\符号隔开,但由于\符号常用于转义,比如\n,\t等,因此目录需要用\隔开,举例:
dir = open("F:\\Documents\\example\\");
返回值:函数返回一个DIR类型的指针,指向参数中给出的具体文件夹地址。
closedir()函数
函数原型:int closedir(DIR *dir);
参数:指向已经打开的文件夹的DIR类型的指针
返回值:
- 关闭成功返回0
- 关闭失败返回1
使用举例
#include <dirent.h>
#include <iostream>
#include <fstream> // 用于执行文件操作的库
using namespace std;
int main(int argc, char **argv)
{
ofstream out("lists.txt"); // 在当前目录建立一个lists.txt的文本文件,如果已有则覆盖
if(!out.is_open())
{
perror("failed to open the lists.txt");
return -1;
}
// 进入正题
DIR *dir;
struct dirent *ent;
dir = opendir(".");
if(dir != NULL)
{
while((ent = readdir(dir)) != NULL)
{
if(ent->d_type == 8) // 仅操作常规文件
{
cout << ent->d_name << endl; // 在屏幕上打印文件名
out << ent->d_name << " "<<argv[1]<<endl; // 这里解决了问题背景部分提出的问题,读者可以删去这行,自行编写处理程序。
}
}
closedir(dir);
out.close(); // 关闭刚才创建的文本文件
}
else
{
perror("fail to open the directory");
return EXIT_FAILURE;
}
cout << "operate successfully!"<<endl;
return 0;
}
利用c++17的特性
这里仅给出引文[3]中的一种方法,笔者未尝试过,给有兴趣的读者尝试
#include <string>
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main()
{
std::string path = "/path/to/directory";
for (const auto & entry : fs::directory_iterator(path))
std::cout << entry.path() << std::endl;
}
引用
[1] 百度百科,dirent词条
[2] Linux下DIR,dirent,stat等结构体详解(转)
[3] How can I get the list of files in a directory using C or C++?