【问题标题】:How to check if a file exists in a given path in C?如何检查文件是否存在于C中的给定路径中?
【发布时间】:2019-09-19 13:55:13
【问题描述】:

我正在尝试在作为参数传递给函数的特定路径中查找文件(比如marks.txt)。是否可以将文件名和路径作为参数提供给检查文件是否存在并打印出路径的函数?

下面的函数只接受路径作为参数。

int fileexists(const char *path){                                 
    File *ptr = fopen(path, "r");                                             
    if (fptr == NULL)                                                                                                                                                                                                      
        return 0;
    fclose(fptr);
    return 1;
}        

所需的函数原型:

int fileexists(const char *path, const char *filename)

【问题讨论】:

  • 该函数的原型看起来如何?像int fileexists(const char *path, const char *filename) 这样的东西?您实际上想要达到什么目的?阅读:XY Problem。请edit您的问题并澄清。
  • 是的,使用 access()faccessat()stat() 之类的东西,但为避免潜在的竞争条件,如果您实际上正在处理文件,最好尝试打开它看看是否成功。
  • @Jabberwocky 是的,函数原型和你提到的一样
  • 那么您应该将这些信息放入问题中。
  • 如果fileexists 返回 1,你的程序会做什么?顺便说一句,您的问题标题与问题不匹配。

标签: c linux file


【解决方案1】:

这个问题有两个部分,正确的答案取决于你想要做什么。

  1. 连接目录名和文件名以形成完整路径名。
  2. 确定文件(由完整路径名引用)是否存在。

连接目录名和文件名很简单。您的朋友strcpystrcat 将完成大部分工作。有一些小细节需要注意: (a) 您需要一个足够大的缓冲区来存储完整路径名,并且您需要决定是否使用固定大小的数组(可能大小为 MAX_PATH) , 或 malloc 的缓冲区; (b) 您可能需要插入一个明确的'/' 字符(即使目录字符串已经以一个结尾,插入一个字符通常也没有什么坏处); (c) 在 Windows 下,您可能希望使用 '\\' 而不是 '/'

然后确定是否存在以完整路径名命名的文件已经在What's the best way to check if a file exists in C? 得到了很好的回答。这里要问的一个大问题是,您是否在询问文件是否存在以准备对文件执行某些操作?如果是这样,如果您检查文件是否存在,那么您就有一个严重的漏洞,但是在您执行其他操作之前,会发生其他事情导致文件出现或消失。因此,与其先检查然后再做,不如尝试做另一件事,优雅地处理任何错误。

【讨论】:

    【解决方案2】:

    您拥有的功能会检查文件是否可以打开,但对于某些存在但您无权打开的文件,它会失败。我会改用stat。要连接路径和文件名,您可以使用字符串函数。

    通常的 Unix C API 很糟糕。正确地做最简单的事情需要付出很多努力——即便如此,我也不确定我没有忘记一些 Unix 主义,比如信号处理或一些晦涩的错误案例。 IE。在现代 C++ 中处理起来相当简单的东西。

    我希望有人设计一个现代 C 系统 API 并至少在 Linux 上实现它,这样我们的痛苦就会结束……

    通常,字符串连接需要一些更高级别的 API 来完成,同时保持一定程度的理智。因此,下面的示例使用strbuilder 类来构建字符串。这使事情变得模糊可读,并避免了最常见的错误。

    #include <assert.h>
    #include <errno.h>
    #include <limits.h>
    #include <stdbool.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    
    struct strbuilder {
        unsigned items, item;
        size_t length, *lengths;
        char *str, *dst;
    };
    
    bool strbuilder_pass(struct strbuilder *builder, int *rc);
    void strcat_str(struct strbuilder *builder, const char *src);
    void strcat_c_ifnone(struct strbuilder *builder, char c);
    bool strbuilder_is_freed(const struct strbuilder *builder);
    
    int fileExists(const char *path, const char *filename)
    {
        const char pathSep = '/';
        int rc;
        struct strbuilder bld = {0};
        while (strbuilder_pass(&bld, &rc))
        {
            strcat_str(&bld, path);
            strcat_c_ifnone(&bld, pathSep);
            strcat_str(&bld, filename);
            if (!rc)
            {
                struct stat statbuf;
                printf("path = %s\n", bld.str);
                rc = stat(bld.str, &statbuf);
            }
        }
        assert(strbuilder_is_freed(&bld));
        return rc;
    }
    
    int main()
    {
        int rc = fileExists("/", "dev");
        assert(rc == 0);
        return 0;
    }
    

    字符串构建由strbuilder_pass 函数控制,该函数通过五次操作推进字符串构建器的状态:

    1. 确定必须存储宽度的项目数(避免调用 strlen 两次)。
    2. 准备长度存储向量。确定所需缓冲区的长度。
    3. 准备输出字符串缓冲区。将元素连接到缓冲区中。
    4. 使用输出字符串缓冲区。
    5. 释放输出字符串缓冲区。

    这个 API 不是特别特别,但适合这个用例。其他一些临时方法也可以,但恕我直言,这更优雅一些。

    void strbuilder_free(struct strbuilder *builder)
    {
        free(builder->lengths);
        free(builder->str);
        memset(builder, 0, sizeof(*builder));
    }
    
    bool strbuilder_pass(struct strbuilder *builder, int *rc)
    {
        if (!builder->length) {// start of pass 1
            builder->length = 1; /*term*/
            *rc = EAGAIN;
            return true;
        }
        else if (!builder->lengths) // end of pass 1
        {
            builder->lengths = malloc(sizeof(*builder->lengths) * builder->items);
            if (builder->lengths)
                return true;
            *rc = ENOMEM;
        }
        else if (!builder->str) // end of pass 2
        {
            builder->dst = (builder->str = malloc(builder->length));
            builder->item = 0;
            builder->length = 0;
            if (builder->dst) {
                *builder->dst = '\0';
                return true;
            }
            *rc = ENOMEM;
        }
        else if (builder->dst) // end of pass 3
        {
            while (*builder->dst) { // include optional content
                builder->dst++; // skip
                builder->length++;
            }
            builder->dst = NULL;
            *rc = 0;
            return true;
        }
        else if (!builder->dst) // end of pass 4 (if any)
        {}
        else {
            *rc = EINVAL;
        }
        strbuilder_free(builder);
        return false;
    }
    
    void strcat_str(struct strbuilder *builder, const char *src)
    {
        if (!src)
            return;
        if (!builder->lengths) // pass 1
            builder->items ++;
        else if (!builder->str) // pass 2
        {
            size_t len = strlen(src);
            builder->lengths[builder->item++] = len;
            builder->length += len;
        }
        else if (builder->dst) // pass 3
        {
            size_t len = builder->lengths[builder->item++];
            if (*builder->dst && (!len || *builder->dst != *src))
            {
                builder->dst++;
                builder->length++;
            }
            memcpy(builder->dst, src, len);
            builder->dst += len;
            builder->length += len;
            *builder->dst = '\0';
        }
    }
    
    void strcat_c_ifnone(struct strbuilder *builder, char c)
    {
        if (!builder->lengths) {} // pass 1
        else if (!builder->str) // pass 2
        {
            if (c) builder->length ++;
        }
        else if (builder->dst) // pass 3
        {
            if (!builder->length || builder->dst[-1] != c)
                *(builder->dst) = c;
        }
    }
    
    bool strbuilder_is_freed(const struct strbuilder *builder)
    {
        return !builder || (!builder->lengths && !builder->str);
    }
    

    【讨论】:

      【解决方案3】:

      你可能想要这样的东西(为了简洁,没有错误检查):

      ...
      #include <string.h>   // for str* functions
      #include <unistd.h>   // for access
      #include <stdlib.h>   // for malloc
      ...
      int fileexists(const char *path, const char *filename)
      {
          char *name= malloc(strlen(path) + strlen(filename) + 1);
          strcpy(name, path);
          strcat(name, filename);
          int retval = access(name, F_OK) == 0;
          free(name);
          return retval;
      }
      

      这样调用:

      if (fileexists("/some/path/", "somefilename.txt")) ...
      

      【讨论】:

      • 在自己使用 Unix C API 2 年之后,我很确定“无错误检查以求简洁”是一个谎言。错误检查是所有复杂性所在。使用现有的 C API 正确完成最基本的事情是多么困难,这是荒谬的。给一个不太清楚如何检查文件是否存在的初学者提供一个带有免责声明“没有错误检查简洁”的解决方案 - 至少为了我的舒适 - 太可能传播到某处的生产代码中,或者给出一个错误检查可能是事后才想到的印象:'(这是我说话的教育家。
      猜你喜欢
      • 2011-07-01
      • 2021-02-17
      • 1970-01-01
      • 1970-01-01
      • 2012-06-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多