【问题标题】:opendir() failing on name with a space, running on linuxopendir() 在名称上失败,带有空格,在 linux 上运行
【发布时间】:2020-02-21 06:42:38
【问题描述】:

我的程序探索当前目录和所有子目录以打印所有文件名。但是,当目录的名称中有空格时,它会产生分段错误。这只发生在 linux - windows 上可以正常工作。

名称存储在dirent.d_name,即char[256]。我尝试使用它,使用c_str() 将其转换为 c 字符串,我尝试将目录名称硬编码到代码中,我尝试转义空间(尽管我不认为我正在这样做正确)。

int main()
{
    struct dirent *direntry;
    dir = opendir( "hello\ world" );
    print_dir_rec( dir, direntry );
    return 0;
}

void print_dir_rec( DIR *dir, struct dirent *direntry )
{
    while( direntry = readdir(dir) )
    {
        switch( direntry->d_type )
        {
            case DT_DIR:
                DIR *sub_dir = opendir( direntry->d_name );
                print_dir_rec( sub_dir , direntry );
                break;
        }
    }
    return;
}

【问题讨论】:

  • dnamereaddir 拥有的存储空间(并且可能会被重复使用),您可以从中复制以将名称保存在其他存储空间中,但您无法转换或对 dname 执行任何操作成员本身,它只是被下一个名称覆盖。
  • 我的意思是我已经尝试从 dname 构造一个字符串,然后传递 string.c_str() 而不是 dname
  • 通常您希望存储在整个递归调用的范围内,例如char path[1024];,然后使用snprintf 创建路径,例如使用name 中的当前目录,您可以使用snprintf(path, sizeof(path), "%s/%s", name, entry->d_name); 不让您的函数使用const char* 而不是DIR *dir,例如print_dir_rec(const char *name) 并进行递归调用print_dir_rec(path)
  • 完成后你从不打电话给closedir()
  • 如果你可以使用 C++17,std::filesystem::recursive_directory_iterator 让这一切变得简单而美好。还有适用于旧版 C++ 的 Boost.Filesystem 版本。

标签: c++ linux directory io


【解决方案1】:

通过将DIR* 指针和struct dirent* 指针作为参数传递而不是简单地形成并传递要打开的下一个路径,您会遇到问题。您希望在递归函数本身中处理目录的打开,而不是作为从 main() 传递的单个指针,例如

void print_dir_rec(const char *name)
{
    DIR *dir;
    struct dirent *entry;

    if (!(dir = opendir(name)))
        return;

    while ((entry = readdir(dir)) != NULL) {
        if (entry->d_type == DT_DIR) {
            char path[1024];
            if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
                continue;
            snprintf(path, sizeof(path), "%s/%s", name, entry->d_name);
            printf("[%s]\n", entry->d_name);
            print_dir_rec(path);
        }
    }
    closedir(dir);
}

注意:您可以根据需要或使用PATH_MAX 宏调整为path 提供的字符数)

这样,在每个导致问题的递归调用中都不会重复使用单个指针。列出当前目录下所有目录的简短示例可能是:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>

void print_dir_rec(const char *name)
{
    DIR *dir;
    struct dirent *entry;

    if (!(dir = opendir(name)))
        return;

    while ((entry = readdir(dir)) != NULL) {
        if (entry->d_type == DT_DIR) {
            char path[1024];
            if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
                continue;
            snprintf(path, sizeof(path), "%s/%s", name, entry->d_name);
            printf("[%s]\n", entry->d_name);
            print_dir_rec(path);
        }
    }
    closedir(dir);
}

int main(void) {
    print_dir_rec(".");
    return 0;
}

看开读是如何处理的,需要传递哪些信息。通过在每个递归调用中为path 提供存储空间,您可以保证该名称在递归调用返回之前一直在作用域内。

如果您还有其他问题,请告诉我。

【讨论】:

  • 非常感谢它完美运行!你能稍微扩展一下 snprintf() 吗?它到底有什么作用,为什么有必要?
  • (抱歉不得不接孩子),sprintf 允许您在这种情况下将 formatted-string 写入缓冲区 (path)。为了将新目录与当前路径结合起来,您必须在它们之间放置 directory-separator。这就是sprintf(或上面使用的snprintf)发挥作用的地方。格式字符串"%s/%s" 允许您将'/' 放在两个字符串之间,将新目录名添加到当前路径的末尾。在 C++ 中,您可以对 std::string 执行相同的操作,只需连接字符串即可。 (虽然具有自动存储持续时间的固定数组会更有效)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-01-09
  • 2016-02-06
  • 1970-01-01
  • 2017-12-08
  • 1970-01-01
  • 1970-01-01
  • 2016-11-11
相关资源
最近更新 更多