广告

一步步教你在 Linux 环境中用 readdir 快速统计目录中的文件数量

快速统计目录中文件数量的原理

本文将介绍在 Linux 环境下,如何借助 readdir 实现对一个目录内的 普通文件数量 的快速统计。核心思路是通过遍历目录流逐条读取条目,并在遍历过程中筛选出真正的文件。通过这种方式,可以在不一次性加载整目录信息的情况下获得统计结果,适合处理大目录的场景。

使用 readdir 的优势在于尽量减少内存占用和系统调用开销,同时避免将整目录的元数据一次性加载到用户态。为了确保统计结果准确,需要额外处理一些细节,例如忽略特殊条目 “.

readdir 的基本工作机制

readdir 接口通过一个 DIR 指针从目录流中逐条读取 struct dirent,每次返回一个条目,直到返回 NULL 表示遍历结束。你可以通过 d_name 获取名称,通过 d_type 判断类型(前提是文件系统提供该信息)。

在实际统计中,常见的做法是先排除特殊条目 "."..",再根据需要统计数量。要点在于对比名称、判断类型,以及在某些文件系统中未携带 d_type 时的回退策略。

#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <errno.h>

int main(int argc, char **argv) {
    const char *dirpath = argc > 1 ? argv[1] : ".";
    DIR *d = opendir(dirpath);
    if (!d) {
        perror("opendir");
        return 1;
    }
    struct dirent *ent;
    unsigned long count = 0;
    while ((ent = readdir(d)) != NULL) {
        if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
        count++;
    }
    printf("%lu\\n", count);
    closedir(d);
    return 0;
}

如何区分普通文件与其他类型

如果你只关心 普通文件,可以先利用 d_type 字段判断条目是否为 DT_REG(普通文件)。这是一个快速的筛选条件,但需要注意并非所有文件系统都会提供已知的 d_type 信息。

d_typeDT_UNKNOWN 时,必须回退到使用 stat() 来判断该条目是否为普通文件。这种回退策略会牵涉到为每个条目构造完整路径并进行系统调用,因此性能会略有下降,但能确保跨文件系统的正确性。

#include <sys/stat.h>
#include <dirent.h>
#include <string.h>

int is_regular_file_dt_unknown(const char *dirpath, const struct dirent *ent) {
    if (ent->d_type == DT_REG) return 1;
    if (ent->d_type == DT_UNKNOWN) {
        char path[256];
        snprintf(path, sizeof(path), "%s/%s", dirpath, ent->d_name);
        struct stat st;
        if (stat(path, &st) == 0 && S_ISREG(st.st_mode)) return 1;
    }
    return 0;
}

在 Linux 环境中实现统计的实际代码

下面给出两种实现方式的示例,帮助你在实际项目中灵活应用。第一种方法使用 d_type 来判定是否为普通文件;第二种方法在 d_typeDT_UNKNOWN 时回退到 stat

通过这两种实现,你可以在 Linux 环境下快速统计目录中的 普通文件数量,并在必要时提供兼容性回退。

方案 A:仅使用 d_type 判断普通文件

这种方案依赖于 d_type 已经被文件系统正确填充,适合对性能敏感且目标环境确定的场景。

优点是无需对每个条目再执行 stat,开销较低。需要注意的是,若某些文件系统未提供 d_type,该方案可能会漏统计。

#include <dirent.h>
#include <stdio.h>
#include <string.h>

unsigned long count_regular_files_dt(const char *dirpath) {
    DIR *d = opendir(dirpath);
    if (!d) return (unsigned long)-1;

    struct dirent *ent;
    unsigned long count = 0;
    while ((ent = readdir(d)) != NULL) {
        if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
        if (ent->d_type == DT_REG) {
            count++;
        }
    }
    closedir(d);
    return count;
}

方案 B:结合 d_type 与 stat 的回退策略

为了实现跨文件系统的鲁棒性,若遇到 d_typeDT_UNKNOWN 的条目,再通过 stat 拼接完整路径进行判断。

该方法在大目录下的统计性能会稍有下降,因为对部分条目需要额外的系统调用来获取元数据。

#include <dirent.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>

int count_regular_files_dt_stat(const char *dirpath) {
    DIR *d = opendir(dirpath);
    if (!d) return -1;

    struct dirent *ent;
    unsigned long count = 0;
    while ((ent = readdir(d)) != NULL) {
        if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;

        if (ent->d_type == DT_REG) {
            count++;
        } else if (ent->d_type == DT_UNKNOWN) {
            char path[PATH_MAX];
            snprintf(path, PATH_MAX, "%s/%s", dirpath, ent->d_name);
            struct stat st;
            if (stat(path, &st) == 0 && S_ISREG(st.st_mode)) {
                count++;
            }
        }
    }
    closedir(d);
    return (int)count;
}

完整示例程序与编译方式

下面提供一个完整的示例程序,结合前述思路:在遍历时优先使用 d_type,若遇到 DT_UNKNOWN 再回退到 stat,并支持通过命令行传入要统计的目录。该程序适用于任何 Linux 环境。

要在本地编译并运行,请将代码保存为 count_files_dir.c,并使用下面的命令进行编译与执行。

编译与运行命令

gcc -O2 count_files_dir.c -o count_files_dir

./count_files_dir /path/to/target

完整示例代码

#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <limits.h>

static int is_regular_file(struct dirent *ent, const char *dirpath) {
    if (ent->d_type == DT_REG) return 1;
    if (ent->d_type == DT_UNKNOWN) {
        char path[PATH_MAX];
        snprintf(path, sizeof(path), "%s/%s", dirpath, ent->d_name);
        struct stat st;
        if (stat(path, &st) == 0 && S_ISREG(st.st_mode)) return 1;
    }
    return 0;
}

unsigned long count_regular_files(const char *dirpath) {
    DIR *d = opendir(dirpath);
    if (!d) return (unsigned long)-1;
    struct dirent *ent;
    unsigned long count = 0;
    while ((ent = readdir(d)) != NULL) {
        if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
        if (is_regular_file(ent, dirpath)) count++;
    }
    closedir(d);
    return count;
}

int main(int argc, char **argv) {
    const char *dirpath = argc > 1 ? argv[1] : ".";
    unsigned long c = count_regular_files(dirpath);
    if (c == (unsigned long)-1) {
        fprintf(stderr, "Failed to open directory: %s\\n", dirpath);
        return 1;
    }
    printf("%lu\\n", c);
    return 0;
}
广告

操作系统标签