【问题标题】:checking directory paths ends with ".", ".."检查目录路径以“.”、“..”结尾
【发布时间】:2025-12-09 11:50:01
【问题描述】:

我有一个编程问题,希望有人能帮我解决。我正在尝试为工作中的一项任务学习 C 编程,并且我为自己设置了一个小项目,其中包括阅读文件树,其中包括获取有关每个文件的信息的所有子目录。

我遇到的问题是我的程序不会忽略以 / 结尾的目录路径。或 /.. 当它打印所有目录时,我想在子目录前面留出空间以提高可读性。

所以这部分出现了错误:

int isDir(const char *parent, char *name) {

    struct stat st_buf; // file info
    char buf[BUF_SIZE];

    if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
        return 0;
    }   
    char *path = malloc(strlen(name) + strlen(parent) + 2);
    //sprintf(char *buf, const char *format, [arg1],[arg2],...)
    sprintf(path, "%s/%s", parent, name);
    stat(path, &st_buf); //

    return S_ISDIR(st_buf.st_mode); //directory
}

这是主函数和列表函数:

int list(const char *name) {
    DIR *dirp = opendir(name);
    struct dirent *dentry;
    char buf[BUF_SIZE];

    while ((dentry = readdir(dirp)) != NULL) {
    char *dir_name = dentry->d_name;        

            printf(" %s\n", dir_name);

        //if it's dir, then go into dir
        if (isDir(name, dir_name)) { //name : parent, dir_name : child
            chdir(dir_name);
            getcwd(buf, BUF_SIZE);
            list(buf);
        }
    }
    closedir(dirp);
}

int main()
{
    list(".");
    return 0;
}

结果是这样的:

hm1.c
Data
lab1.txt
result1
lab3.txt
.
..
.
..
result2
lab3.txt
.
..
result3
lab3.txt
.
..
a.c
.
..
a.out

我要打印的结果

hm1.c
Data
    lab1.txt
    result1
        lab3.txt
    result2
        lab3.txt
    result3
        lab3.txt
a.c
a.out

【问题讨论】:

  • 您的 isDir() 函数每次在 name 中使用 ... 以外的其他内容调用时都会泄漏内存。您还应该至少检查 malloc()opendir() — 也可以说是 chdir()getcwd()
  • 在打印任何内容之前,您需要额外检查 ...,并且您可能需要将额外的深度参数传递给 list,以便您可以缩进基于它。
  • printf(" %s\n", dir_name); 中使用的dir_name 在哪里定义、设置等? file_modemy_passwd 变量也未使用。请参阅有关制作 MCVE 的指南 (minimal reproducible example)。它们使人们更容易帮助您。您显示的代码似乎比原始代码有所减少,但并未最小化且无法验证。
  • 在打印任何内容之前,我对它们进行了额外的检查,但这也不起作用:(深度参数是什么?@RetiredNinja
  • while ((dentry = readdir(dirp)) != 0) 循环中,在打印任何内容之前使用if (strcmp(dentry->d_name, ".") == 0 || strcmp(dentry->d_name, "..") continue;——这样做也可以简化isDir()。 'depth' 参数将由list(".", 0);main()int list(const char *dir, int level) { …; list(buf, level + 1); … } 中触发。您将使用级别编号来生成适当数量的空格。例如,printf("%.*s", level * 4, ""); 将在每个级别生成 4 个空格。

标签: c linux


【解决方案1】:

如果您有 ...,则您的 isDir 返回 true/false,在其他情况下返回 false(或零),然后在其他情况下返回 S_ISDIR 的 true/false 值

您真正需要的是返回 SKIP、isFILE 或 isDIR 3 个值之一的函数,然后在此基础上编写打印逻辑。

您还需要修复内存泄漏

还要注意chdir(dir_name); 会更改进程的实际目录,因此一旦您在循环中从list 返回,您将无法再打开您正在循环的文件或目录(因为您现在在不同的目录中)

这将解决您的问题并打印您想要的格式

enum { doSkip, isFile, isDir } testDir(char *path, char *name) 
{ 
     struct stat st_buf;        
     if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
         return doSkip;
     }   
     stat(path, &st_buf);
     if (S_ISDIR(st_buf.st_mode))
         return isDir;
     return isFile;
}

void list(const char *path, int indentlevel)
{
     DIR *dirp = opendir(path);
     struct dirent *dentry;
     char buf[10000]; // Lets just make the buffer sufficently big for this example
     if (!dirp) {
      printf("%*sNo access\n",indentlevel,"");
      return;
     }

     while ((dentry = readdir(dirp)) != NULL) {

        sprintf(buf,"%s/%s", path, dentry->d_name);
        switch (testDir(buf,dentry->d_name)) {
        case doSkip:
           /* do nothing */
           break;
        case isDir:
           printf("%*s%s:\n",indentlevel,"",dentry->d_name);
           list(buf,indentlevel+4);
           break;
        case isFile:
           printf("%*s%s\n",indentlevel,"",dentry->d_name);
           break;
        }
     }
     closedir(dirp);
}

int main()
{
     list(".", 0);
     return 0;
}

【讨论】:

    【解决方案2】:

    如果您愿意使用 C++,另一种方法是使用std::experimental::filesystem,也(主要)称为Boost.Filesystem。有了这个,你会做这样的事情:

    #include <experimental/filesystem> // Could substitute <boost/filesystem.hpp>
    #include <boost/range/iterator_range.hpp>
    #include <iostream>
    
    using namespace std::experimental;
    
    int main(int argc, char *argv[]) 
    {
        const auto path = filesystem::path{ argc > 1 ? argv[1] : "." };
    
        if( filesystem::is_directory(path) ) 
        {
            std::cout << path << " is a directory containing:\n";
    
            for( const auto& entry : boost::make_iterator_range( filesystem::recursive_directory_iterator{path}, {} ) )
            {
                std::cout << entry << "\n";
            }
        }
    }
    

    看到它运行here。请注意,目录迭代器会自动跳过...

    【讨论】:

    • 你的答案是 C++ -- OP 在问一个 C 问题
    • 对,这就是为什么我说“如果你愿意去 C++”。由于 OP 已经为他/她自己设定了这个任务,C 似乎不是一个硬性要求,并且 C++ 具有跨平台、几乎标准的处理它的工具。事实上,它显式地依赖于 Linux/POSIX 函数。我的解决方案不需要并且需要相当少的代码来正确和维护。
    • 我试图看看我是否可以让您的示例工作以理解递归迭代器的 magic,并查看输出是否可以按照规范进行格式化 - - 但是我遇到了类似这样的提升链接问题的兔子洞*.com/questions/15634114/… - 我必须说,我从来都不是提升的粉丝,所以我在无法让它运行的情况下放弃了
    • 我的示例在提供的链接中使用 std::experimental 运行。正确构建 Boost 可能很棘手,但您也可以从Linux 发行版安装它并使用它。