Linux系统编程之目录遍历

概述

目录遍历是指以递归或循环方式,逐个访问文件系统中的文件和子目录,这通常用于实现文件搜索、备份工具、同步服务等功能。为了有效地遍历目录,Linux提供了多种方法,最常见的是:readdir和scandir。下面,我们将分别进行介绍。

readdir

readdir函数通常与opendir、closedir结合使用,以遍历某个目录中的所有条目。每次调用readdir时,会返回下一个条目,直到没有更多条目为止。这三个函数均在头文件"dirent.h"中,其原型如下。

cpp 复制代码
DIR *opendir(const char *name);

int closedir(DIR *dirp);

struct dirent *readdir(DIR *dirp);

opendir函数各个参数和返回值的含义如下。

name: 指向要打开的目录路径名的指针。可以是绝对路径(从根 / 开始)或相对路径(相对于当前工作目录)。

返回值:成功时返回指向DIR结构的指针,表示新打开的目录流。失败时返回NULL,并设置errno来指示具体的错误类型。

closedir函数各个参数和返回值的含义如下。

dirp: 由opendir函数返回的目录流指针。

返回值:成功时返回0,失败时返回-1,并设置errno来指示具体的错误类型。

readdir函数各个参数和返回值的含义如下。

dirp: 由opendir函数返回的目录流指针。

返回值:成功时返回指向struct dirent的指针,包含当前条目的信息。失败时返回NULL,并设置errno来指示具体的错误类型。

在下面的示例代码中,我们首先定义了一个list_dir函数。该函数接收一个路径作为参数,尝试打开该路径所指向的目录,并逐个读取目录中的条目,将每个条目的名称打印出来。main函数会检查命令行参数以确定要列出的目录,默认情况下是当前工作目录,最后调用list_dir函数执行目录遍历任务。

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <errno.h>
#include <string.h>

void list_dir(const char *path)
{
    // 打开目录
    DIR *pDir = opendir(path);
    if (pDir == NULL)
    {
        printf("open directory failed: %s\n", strerror(errno));
        return;
    }

    // 读取并列出目录内容
    struct dirent *pEntry = NULL;
    while ((pEntry = readdir(pDir)) != NULL)
    {
        printf("%s\n", pEntry->d_name);
    }

    // 关闭目录
    closedir(pDir);
}

int main(int argc, char *argv[])
{
    const char* pszDirPath = (argc > 1) ? argv[1] : ".";
    list_dir(pszDirPath);
    return 0;
}

scandir

scandir函数允许我们一次性读取目录中的所有条目,并且可以根据指定的条件进行筛选和排序。其函数原型如下。

cpp 复制代码
int scandir(const char *dirp, struct dirent ***namelist,
    int (*filter)(const struct dirent *),
    int (*compar)(const struct dirent **, const struct dirent **));

dirp:指向要扫描的目录路径名的指针。

namelist:指向一个指针数组的指针,用来存放返回的条目,每个元素是指向struct dirent的指针。

filter:可选的过滤函数,用于筛选哪些条目应该被包含。如果为NULL,则不过滤。

compar:可选的比较函数,用于对结果进行排序。如果为NULL,则不排序。

返回值:成功时返回匹配条目的数量,失败时返回-1,并设置errno来指示具体的错误类型。

在下面的示例代码中,我们首先定义了一个过滤函数filter,该函数仅允许普通文件通过。在主函数中,我们调用scandir函数,传入目录路径、用于存储结果的指针数组namelist、过滤函数和排序函数。如果成功执行,我们就遍历返回的条目列表,逐个打印文件名,并释放每个条目以及整个列表分配的内存,确保没有内存泄漏。

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <fnmatch.h>
#include <errno.h>
#include <string.h>

int filter(const struct dirent *entry)
{
    return entry->d_type == DT_REG;
}

int main()
{
    const char* dirPath = "./";
    struct dirent **namelist;
    // 使用filter函数筛选出普通文件,并按字母顺序排序
    int n = scandir(dirPath, &namelist, filter, alphasort);
    if (n < 0)
    {
        printf("scandir failed: %s\n", strerror(errno));
        return 1;
    }
    else
    {
        for (int i = 0; i < n; ++i)
        {
            printf("%s\n", namelist[i]->d_name);
            // 分配的内存需要手动释放
            free(namelist[i]);
        }
        
        // 释放namelist数组本身
        free(namelist);
    }

    return 0;
}

总结

可以看到,readdir和scandir都可以用于读取目录内容。但它们的工作方式和使用场景有所不同,主要可以归纳为以下几点。

1、使用场景。

readdir:逐个读取目录条目,适用于简单的遍历操作。

scandir:一次性读取所有条目到内存中,对于需要筛选或排序的情况更加高效,因为它减少了重复访问磁盘的需求。

2、灵活性。

readdir:较为简单,适合基本需求。

scandir:提供了更多的控制选项,比如过滤和排序功能。

3、资源管理。

readdir:需要使用closedir及时关闭目录流。

scandir:必须记得释放分配给namelist的内存,以避免内存泄漏。

相关推荐
AOwhisky2 小时前
Kubernetes 学习笔记:集群管理、命名空间与 Pod 基础
linux·运维·笔记·学习·云原生·kubernetes
小龙在慢慢变强..2 小时前
目录结构(FHS 标准)
linux·运维·服务器
2035去旅行2 小时前
嵌入式开发,如何选择C标准库
linux·arm开发
刘延林.2 小时前
win11系统下通过 WSL2 安装Ubuntu 24.04 使用RTX 5080 GPU
linux·运维·ubuntu
CodeOfCC4 小时前
Linux 嵌入式arm64安装openclaw
linux·运维·服务器
宵时待雨5 小时前
linux笔记归纳3:linux开发工具
linux·运维·笔记
magrich5 小时前
安装NoMachine并解决无外接显示器桌面黑屏
linux·运维·服务器
fish_xk5 小时前
Linus基础指令
linux·服务器
宁波阿成5 小时前
在ubuntu22.04源码级安装sub2api
linux·运维·ubuntu·ai·api·token·中转站
charlie1145141916 小时前
嵌入式Linux驱动开发(7) 从虚拟设备到真实硬件 —— LED驱动硬件基础
linux·开发语言·驱动开发·内核·c