检查文件是否存在于C中的最佳方法是什么?


436

是否有比单纯尝试打开文件更好的方法?

int exists(const char *fname)
{
    FILE *file;
    if ((file = fopen(fname, "r")))
    {
        fclose(file);
        return 1;
    }
    return 0;
}

我认为我会给出访问方法的答案,尽管stat方法是一种非常合理的替代方法,但是访问可以完成工作。
戴夫·马歇尔

1
真的只想检查是否存在吗?还是要检查并写入文件(如果尚不存在)。如果是这样,请参阅下面的答案,以获取不受竞争条件影响的版本。
丹·伦斯基

6
我看不到-这种fopen / fclose方式怎么了?
Johannes Schaub-litb

16
@ JohannesSchaub-litb:fopen()/ fclose()方法的错误之处在于,即使该文件存在,您也可能无法打开文件进行读取。例如,/dev/kmem存在,但是大多数进程甚至都无法打开它以供阅读。 /etc/shadow是另一个这样的文件。当然,无论是stat()access()依靠的是能够访问包含该文件的目录; 如果您不能这样做,则所有选择均无效(对包含文件的目录没有执行权限)。
Jonathan Leffler

1
if (file = fopen(fname, "r"))会发出警告。在if语句中的语句周围使用括号if ((file = fopen(fname, "r")))
-Joakim

Answers:


595

查找中的access()功能unistd.h。您可以将功能替换为

if( access( fname, F_OK ) != -1 ) {
    // file exists
} else {
    // file doesn't exist
}

您还可以使用R_OKW_OKX_OK代替F_OK来分别检查读取许可权,写入许可权和执行许可权,而不是是否存在,并且您可以将它们中的任何一个或在一起(即使用来检查读取许可权写入许可权R_OK|W_OK

更新:请注意,在Windows上,W_OK由于访问功能未考虑DACL ,因此不能可靠地测试写入权限。access( fname, W_OK )可能会返回0(成功),因为该文件未设置只读属性,但是您仍然没有写该文件的权限。


67
POSIX是ISO标准;它定义了access()。C是另一个ISO标准;它不是。
乔纳森·莱夫勒

16
有一些与access()相关的陷阱。在使用access()与随后执行的其他操作之间,存在一个TOCTOU(检查时间,使用时间)窗口,该窗口存在漏洞。[...待续...]
乔纳森·勒夫勒

23
[...继续...]更确切地说,在POSIX系统上,access()检查是否是真实的UID和真实的GID,而不是有效的UID和有效的GID。这仅对setuid或setgid程序很重要,但随后很重要,因为它可能会给出“错误”的答案。
乔纳森·勒夫勒

3
寻找access()代码中断的原因时,我遇到了这个问题。我从DevC ++移到了CodeBlocks,它停止工作了。因此,它不是万无一失的。向@Leffler +1。

11
在大多数情况下,是的(可以access()用来检查文件的存在),但是在SUID或SGID程序中,即使那样可能也不正确。如果测试的文件位于真实UID或真实GID无法访问的目录中,则access()当该文件存在时,可能不会报告该文件。深奥而不可能?是。
乔纳森·莱夫勒

116

stat像这样使用:

#include <sys/stat.h>   // stat
#include <stdbool.h>    // bool type

bool file_exists (char *filename) {
  struct stat   buffer;   
  return (stat (filename, &buffer) == 0);
}

并这样称呼它:

#include <stdio.h>      // printf

int main(int ac, char **av) {
    if (ac != 2)
        return 1;

    if (file_exists(av[1]))
        printf("%s exists\n", av[1]);
    else
        printf("%s does not exist\n", av[1]);

    return 0;
}

4
@LudvigANorin:在这样的系统上,可能access()还会出现问题,并且有一些选项可用于制作access()stat()使用大文件(大于2 GB)。
乔纳森·莱夫勒

14
你们两个都可以指向有关2 GB后故障的文档吗?另外,在这种情况下还有什么选择?
chamakits

@JonathanLeffler是否stat不会遭受与TOCTOU相同的漏洞access?(我不清楚是否会更好。)
Telemachus 2014年

9
双方stat()access()从TOCTOU漏洞遭受(也是如此lstat(),而且fstat()是安全的)。根据文件的存在与否,这取决于您要执行的操作。使用正确的选项open()通常是解决问题的最佳方法,但是制定正确的选项可能会很棘手。另请参见有关EAFP(比请求更容易获得宽恕)和LBYL(请先了解一下)的讨论- 例如,参见Java中的LBYL vs EAFP
乔纳森·勒夫勒

87

通常,当您要检查文件是否存在时,是因为要创建该文件(如果不存在)。 如果您不想创建该文件,Graeme Perrow的回答很好,但是如果这样做,它很容易受到竞争条件的影响:另一个过程可以在您检查文件是否存在之间创建一个文件,然后您实际打开该文件以写入文件。(不要笑... 如果创建的文件是符号链接,可能会对安全性造成不良影响!)

如果要检查是否存在创建文件(如果不存在)(原子方式),以便不存在争用条件,请使用以下命令:

#include <fcntl.h>
#include <errno.h>

fd = open(pathname, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
if (fd < 0) {
  /* failure */
  if (errno == EEXIST) {
    /* the file already existed */
    ...
  }
} else {
  /* now you can use the file */
}

8
如果要使用O_CREAT,则需要提供模式(权限)作为open()的第三个参数。还应考虑应使用O_TRUNC还是O_EXCL或O_APPEND。
乔纳森·勒夫勒

6
乔纳森·莱夫勒(Jonathan Leffler)是对的,此示例需要O_EXCL像编写的那样工作。
兰迪·普罗克

6
另外,您需要将模式指定为第三个参数:open(lock,O_CREAT | O_WRONLY | O_EXCL,S_IRUSR | S_IWUSR)
andrew

4
应当注意,这仅与文件系统兼容POSIX一样安全。特别是,旧版本的NFS具有必须避免的O_EXCL竞争条件!有一种解决方法,以open(2)(在Linux上;您的OS的手册页可能有所不同)中记录,但是它很丑陋,可能无法抵抗恶意攻击者。
凯文

请注意,要将此与配合使用FILE*,则需要使用posix方法fdopen(fd,"flags")来生成FILE*
Gem Taylor

32

是。使用stat()。请参见手册页stat(2)

stat()如果文件不存在将失败,否则很可能成功。如果该目录确实存在,但是您对该目录所在的目录没有读取权限,则该目录也会失败,但是在这种情况下,任何方法都会失败(如何检查根据访问权限可能看不到的目录的内容?简而言之,你不能。

哦,正如别人提到的,您也可以使用access()。但是,我更喜欢stat()好像文件存在一样,它将立即为我提供许多有用的信息(上次更新时间,文件的大小,拥有该文件的所有者和/或组,访问权限等)。


5
如果只需要知道文件是否存在,则可以进行访问。如果您不需要所有额外的信息,则Stat()可能会被大肆窃听。
Martin Beckett

4
实际上,当我使用ls-command列出目录时,它会为该目录中存在的每个文件调用stat,而运行ls会产生很大的开销对我来说是很新的。实际上,您可以在具有成千上万个文件的目录上运行ls,并且它会在不到一秒钟的时间内返回。
麦基

2
@Mecki:与支持硬链接的系统上的访问相比,stat具有非零的额外开销。这是因为访问仅需查看目录条目,而stat也必须查看索引节点。在查找时间较差的存储设备(例如磁带)上,差异可能会很大,因为目录条目和索引节点不太可能彼此相邻。
凯文

3
@Kevin除非仅将F_OK传递给它,否则请access()检查文件的文件访问权限,并且这些权限将存储在该文件的inode中,而不是在其目录条目中(至少对于具有inode类结构的所有文件系统而言) 。因此,access()必须以与访问索引节点完全相同的方式来访问索引节点stat()因此,您所说的只有在不检查任何权限的情况下才成立!而且实际上在某些系统access()上甚至是在其之上实现的stat()(例如,GNU Hurd上的glibc就是这样做的),因此首先并不能保证。
Mecki '16

@Mecki:谁说什么有关检查权限?我在专门谈论F_OK。是的,某些系统实施不佳。在每种情况下,访问速度至少都会与stat一样快,并且有时可能会更快。
凯文

9
FILE *file;
    if((file = fopen("sample.txt","r"))!=NULL)
        {
            // file exists
            fclose(file);
        }
    else
        {
            //File not found, no memory leak since 'file' == NULL
            //fclose(file) would cause an error
        }

1
这会不会导致内存泄漏?如果文件存在,则永远不会关闭它。
LegionMammal978 '16

1
这是一个很好的简单方法。如果您使用的是Windows MSVC,请改用此方法:(fopen_s(file, "sample.txt", "r"))因为fopen()已被视为已弃用(或禁用已弃用的错误,但不建议这样做)。
Nikos

15
fopen()是标准C语言,它不会随处可见。它只是被Microsoft“弃用”。fopen_s()除非需要平台特定的,不可移植的代码,否则不要使用。
安德鲁·亨勒

调用fclose()一无所有?首先需要分配变量“文件”!
Jenix

1
此处的变量“文件”具有垃圾值。为什么首先要麻烦关闭它?你只是叫'FCLOSE(SOME_RANDOM_ADDRESS);' ..
Jenix

6

从Visual C ++帮助中,我倾向于

/* ACCESS.C: This example uses _access to check the
 * file named "ACCESS.C" to see if it exists and if
 * writing is allowed.
 */

#include  <io.h>
#include  <stdio.h>
#include  <stdlib.h>

void main( void )
{
   /* Check for existence */
   if( (_access( "ACCESS.C", 0 )) != -1 )
   {
      printf( "File ACCESS.C exists\n" );
      /* Check for write permission */
      if( (_access( "ACCESS.C", 2 )) != -1 )
         printf( "File ACCESS.C has write permission\n" );
   }
}

还值得注意的模式值:_access(const char *path,int mode)

  • 00:仅存在

  • 02:写许可

  • 04:读取权限

  • 06:读写权限

身为你的 fopen可能在文件存在但无法按要求打开的情况下失败。

编辑:刚读梅基的帖子。 stat()确实看起来更整洁。哼


如果只需要知道文件是否存在,则可以进行访问。Stat()可能会听到很大的声音。
Martin Beckett

4

您可以使用realpath()函数。

resolved_file = realpath(file_path, NULL);
if (!resolved_keyfile) {
   /*File dosn't exists*/
   perror(keyfile);
   return -1;
}

3

我认为可以找到的access()函数unistd.h是一个不错的选择Linux(您也可以使用stat)。

您可以像这样使用它:

#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>

void fileCheck(const char *fileName);

int main (void) {
    char *fileName = "/etc/sudoers";

    fileCheck(fileName);
    return 0;
}

void fileCheck(const char *fileName){

    if(!access(fileName, F_OK )){
        printf("The File %s\t was Found\n",fileName);
    }else{
        printf("The File %s\t not Found\n",fileName);
    }

    if(!access(fileName, R_OK )){
        printf("The File %s\t can be read\n",fileName);
    }else{
        printf("The File %s\t cannot be read\n",fileName);
    }

    if(!access( fileName, W_OK )){
        printf("The File %s\t it can be Edited\n",fileName);
    }else{
        printf("The File %s\t it cannot be Edited\n",fileName);
    }

    if(!access( fileName, X_OK )){
        printf("The File %s\t is an Executable\n",fileName);
    }else{
        printf("The File %s\t is not an Executable\n",fileName);
    }
}

您将获得以下输出:

The File /etc/sudoers    was Found
The File /etc/sudoers    cannot be read
The File /etc/sudoers    it cannot be Edited
The File /etc/sudoers    is not an Executable
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.