Linux C语言非递归遍历指定目录下的所有文件及目录

Linux使用C语言进行目录遍历操作所需要的相关函数及知识,在下面的链接中有详细的介绍,这里不再赘述。

Linux C语言递归遍历指定目录下的所有文件及目录

下面主要介绍非递归写法

非递归实现遍历的主要思想就是使用栈或队列保存待遍历的元素。

如果是深度优先遍历,应使用栈保存;如果是宽度优先遍历,应使用队列保存。

算法的主要步骤如下:

  1. 将当前目录下的所有元素加入栈或队列中
  2. 如果栈或队列不为空,则重复执行下述操作:
  3. 取出一个栈或队列中的元素
  4. 如果该元素为文件,则正常访问;如果该元素为目录,则将该目录下的所有元素加入栈或队列

确定数据结构

  1. 队列
    考虑到现实中的目录结构,这里采用队列实现。即优先访问当前目录下的文件,当文件遍历完成后,再去遍历当前目录下的子目录。
  2. 链表
    由于文件及目录的数量未知,可能很多。如果使用数组作为队列的实现,可能出现数组长度不够的情况。因此采用更为灵活的链表来实现队列。
  3. 双向链表
    队列先进后出的特点要求在对链表进行操作时,要在头部弹出、尾部插入。由于需要使用尾插法,使用双向链表实现较为方便。
  4. 有头节点的双向循环链表
    为了保证操作的统一性,最终决定使用有头结点的双向循环链表来实现。因为如果使用非循环的链表,在插入和弹出时,均需要对链表为空的情况做单独处理(可以回忆一下双向链表的插入和删除操作);同时还需要额外维护一个尾指针。这非常的不优雅。而使用循环链表则不会有上述问题,只需维护一个头节点即可,在链表空和非空时的插入和弹出操作都是完全相同的代码。

代码实现

  • 首先是节点的结构体定义
typedef struct EntNode
{
	struct dirent ent;
	struct EntNode* next;
	struct EntNode* pre;
	char path[MAX_FILENAME_LENGTH];
}EntNode;

注意:这里额外定义了一个path数组用于记录当前结点所属目录的路径。这是因为在遍历时,我们需要使用opendir函数来获得DIR结构体的指针,因此需要将目录的路径信息保存在节点中。与递归版本的代码进行对照的不难发现,这一数组就相当于递归版本中在进行递归时传入路径参数的行为。

  • 接下来是链表相关操作的函数。
static int isEmpty(EntNode* head)
{
	return (head->next == head);
}

// 插入尾部
static int push(EntNode* head, struct dirent ent, char* dirPath)
{
	EntNode* node = (EntNode*)malloc(sizeof(EntNode));	
	if (node == NULL) return 0;

	node->ent = ent;
	strcpy(node->path, dirPath);
	
	node->pre = head->pre;
	node->next = head;
	head->pre->next = node;
	head->pre = node;

	return 1;
}

// 从头部弹出
static int pop(EntNode* head, struct EntNode* node)
{
	if (isEmpty(head)) return 0;
	
	EntNode* tmp = head->next;
	tmp->next->pre = head;
	head->next = tmp->next;
	*node = *tmp;

	free(tmp);	
	return 1;

}

static int pushAll(char* dirPath, EntNode* head)
{
	// Bad address
	if (!dirPath || !strlen(dirPath))
	{
		return  EFAULT;
	}

	//  获得目录结构体指针
	DIR* pDir = opendir(dirPath);
	if (pDir == NULL)
	{
		return EFAULT;
	}

	// 遍历该目录下文件,将所有对象加入到队列中
	struct dirent* ent = readdir(pDir);
	int err = 0;
	while (ent)
	{
		if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
		{
			ent = readdir(pDir);
			continue;
		}		
		// 加入队列
		if (!push(head, *ent, dirPath)) 
		{
			err = ENOBUFS;
			break;
		}
		ent = readdir(pDir);
	}
	closedir(pDir);
	return err;
}
  • 遍历的函数

该函数成功时返回0,失败时返回错误码。

int dirWalk(char* dirpath, void* ctx)
{
	// 创建队列
	EntNode* head = (EntNode*)malloc(sizeof(EntNode));
	if (head == NULL)
	{
		return ENOBUFS;
	}
	head->next = head;
	head->pre = head;

	// 将当前目录下所有对象加入队列
	int err = pushAll(dirpath, head);
	if (err) return err;

	// 不断读取队列,直至队列为空
	struct EntNode node;
	while (!isEmpty(head))
	{
		pop(head, &node);
		// 判断目标路径是否过长
		if (strlen(node.path) + node.ent.d_reclen + 1 >= MAX_FILENAME_LENGTH)
		{
			return ENAMETOOLONG;
		}
		// 拼接出目标目录的路径
		char targetPath[MAX_FILENAME_LENGTH];
		sprintf(targetPath, "%s%c%s", node.path, FILE_SEPERATOR, node.ent.d_name);

		// 目录,将该目录下的所有实例加入队列
		if (node.ent.d_type == DT_DIR)
		{
			err = pushAll(targetPath, head);
			if (err != 0) break;
		}
		// 文件,做你想做的事
		else if (node.ent.d_type == DT_REG)
		{
			// TODO		
		}
	}

	return err;
}

以上是非递归的代码实现。

这里双向循环链表是自己定义的,如果要考虑编程的规范性和统一性,应该使用#include <sys/queue.h>文件中提供的TAILQ(双向有尾链表)或CIRCLEQ(双向循环链表)进行实现。我会在下一篇文章中介绍。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值