c - 如何在 Linux 上递归列出 C 中的目录?

我需要递归列出 C 编程中的所有目录和文件。我研究过 FTW,但我正在使用的 2 个操作系统(Fedora 和 Minix)中不包含它。我开始对过去几个小时阅读的所有不同内容感到头疼。

如果有人知道我可以查看的代码片段,那将是惊人的,或者如果有人可以在这方面给我很好的指导,我将非常感激。

最佳答案

为什么每个人都坚持一次又一次地重新发明轮子?

POSIX.1-2008 标准化 nftw()函数,也在 Single Unix Specification v4 (SuSv4) 中定义,可在 Linux(glibc、man 3 nftw)、OS X 和大多数最新的 BSD 变体中使用。一点都不新鲜。

Naïve opendir()/readdir()/closedir() 基于实现几乎从不处理目录或文件被移动的情况,在树遍历期间重命名或删除,而 nftw() 应该优雅地处理它们。

例如,考虑以下 C 程序,它列出了从当前工作目录开始的目录树,或者在命令行命名的每个目录,或者只是在命令行命名的文件:

/* We want POSIX.1-2008 + XSI, i.e. SuSv4, features */
#define _XOPEN_SOURCE 700

/* Added on 2017-06-25:
   If the C library can support 64-bit file sizes
   and offsets, using the standard names,
   these defines tell the C library to do so. */
#define _LARGEFILE64_SOURCE
#define _FILE_OFFSET_BITS 64 

#include <stdlib.h>
#include <unistd.h>
#include <ftw.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

/* POSIX.1 says each process has at least 20 file descriptors.
 * Three of those belong to the standard streams.
 * Here, we use a conservative estimate of 15 available;
 * assuming we use at most two for other uses in this program,
 * we should never run into any problems.
 * Most trees are shallower than that, so it is efficient.
 * Deeper trees are traversed fine, just a bit slower.
 * (Linux allows typically hundreds to thousands of open files,
 *  so you'll probably never see any issues even if you used
 *  a much higher value, say a couple of hundred, but
 *  15 is a safe, reasonable value.)
*/
#ifndef USE_FDS
#define USE_FDS 15
#endif

int print_entry(const char *filepath, const struct stat *info,
                const int typeflag, struct FTW *pathinfo)
{
    /* const char *const filename = filepath + pathinfo->base; */
    const double bytes = (double)info->st_size; /* Not exact if large! */
    struct tm mtime;

    localtime_r(&(info->st_mtime), &mtime);

    printf("%04d-%02d-%02d %02d:%02d:%02d",
           mtime.tm_year+1900, mtime.tm_mon+1, mtime.tm_mday,
           mtime.tm_hour, mtime.tm_min, mtime.tm_sec);

    if (bytes >= 1099511627776.0)
        printf(" %9.3f TiB", bytes / 1099511627776.0);
    else
    if (bytes >= 1073741824.0)
        printf(" %9.3f GiB", bytes / 1073741824.0);
    else
    if (bytes >= 1048576.0)
        printf(" %9.3f MiB", bytes / 1048576.0);
    else
    if (bytes >= 1024.0)
        printf(" %9.3f KiB", bytes / 1024.0);
    else
        printf(" %9.0f B  ", bytes);

    if (typeflag == FTW_SL) {
        char   *target;
        size_t  maxlen = 1023;
        ssize_t len;

        while (1) {

            target = malloc(maxlen + 1);
            if (target == NULL)
                return ENOMEM;

            len = readlink(filepath, target, maxlen);
            if (len == (ssize_t)-1) {
                const int saved_errno = errno;
                free(target);
                return saved_errno;
            }
            if (len >= (ssize_t)maxlen) {
                free(target);
                maxlen += 1024;
                continue;
            }

            target[len] = '\0';
            break;
        }

        printf(" %s -> %s\n", filepath, target);
        free(target);

    } else
    if (typeflag == FTW_SLN)
        printf(" %s (dangling symlink)\n", filepath);
    else
    if (typeflag == FTW_F)
        printf(" %s\n", filepath);
    else
    if (typeflag == FTW_D || typeflag == FTW_DP)
        printf(" %s/\n", filepath);
    else
    if (typeflag == FTW_DNR)
        printf(" %s/ (unreadable)\n", filepath);
    else
        printf(" %s (unknown)\n", filepath);

    return 0;
}


int print_directory_tree(const char *const dirpath)
{
    int result;

    /* Invalid directory path? */
    if (dirpath == NULL || *dirpath == '\0')
        return errno = EINVAL;

    result = nftw(dirpath, print_entry, USE_FDS, FTW_PHYS);
    if (result >= 0)
        errno = result;

    return errno;
}

int main(int argc, char *argv[])
{
    int arg;

    if (argc < 2) {

        if (print_directory_tree(".")) {
            fprintf(stderr, "%s.\n", strerror(errno));
            return EXIT_FAILURE;
        }

    } else {

        for (arg = 1; arg < argc; arg++) {
            if (print_directory_tree(argv[arg])) {
                fprintf(stderr, "%s.\n", strerror(errno));
                return EXIT_FAILURE;
            }
        }

    }

    return EXIT_SUCCESS;
}

上面的大部分代码都在 print_entry() 中。它的任务是打印出每个目录条目。在 print_directory_tree() 中,我们告诉 nftw() 为它看到的每个目录条目调用它。

上面唯一的手波细节是关于应该让 nftw() 使用多少个文​​件描述符的决定。如果您的程序在文件树遍历期间最多使用两个额外的文件描述符(除了标准流),则已知 15 是安全的(在所有具有 nftw() 并且主要是 POSIX-合规)。

在 Linux 中,您可以使用 sysconf(_SC_OPEN_MAX) 找到打开文件的最大数量,并减去与 nftw() 调用同时使用的数量,但我不会打扰(除非我知道该实用程序主要用于病态深层目录结构)。十五个描述符限制了树的深度; nftw() 只是变得更慢(并且如果从目录中遍历的目录深度超过 13 个目录,则可能无法检测到目录中的更改,尽管权衡和检测更改的一般能力在系统和 C 库实现之间有所不同)。仅仅使用这样的编译时常量就可以保持代码的可移植性——它不仅可以在 Linux 上工作,而且可以在 Mac OS X 和所有当前的 BSD 变体以及大多数其他不太旧的 Unix 变体上工作。

在评论中,Ruslan 提到他们必须切换到 nftw64() 因为他们的文件系统条目需要 64 位大小/偏移量,以及 nftw 的“正常”版本() 失败,出现 errno == EOVERFLOW。正确的解决方案是不要切换到 GLIBC 特定的 64 位函数,而是定义 _LARGEFILE64_SOURCE_FILE_OFFSET_BITS 64。这些告诉 C 库尽可能切换到 64 位文件大小和偏移量,同时使用标准函数(nftw()fstat() 等)和类型名称(off_t 等)。

https://stackoverflow.com/questions/8436841/

相关文章:

linux - 在 Linux 的文本文件中用逗号替换空格

python - 如何跳过循环中的迭代?

linux - 在 Linux 中将多个 JPG 合并为单个 PDF

mysql - 通过 shell 脚本使用 echo 命令自动化 mysql_secure_inst

python - 在 ipython 笔记本中测量单元执行时间的简单方法

python - 使用 Pandas 计算每个组的唯一值

python - python的Maven等价物

linux - 在 shell 脚本中缩进多行输出

linux - SO_REUSEADDR(setsockopt 选项)的含义是什么 - Linux?

android - 如何在 Ubuntu 上安装 Android SDK?