Qual é a melhor maneira de verificar se existe um arquivo em C?

436

Existe uma maneira melhor do que simplesmente tentar abrir o arquivo?

int exists(const char *fname)
{
    FILE *file;
    if ((file = fopen(fname, "r")))
    {
        fclose(file);
        return 1;
    }
    return 0;
}
Dave Marshall
fonte
Acho que darei a resposta ao método de acesso, apesar de o método stat ser uma alternativa muito razoável, o acesso faz o trabalho.
Dave Marshall
1
Você realmente quer apenas verificar a existência? Ou você deseja verificar e gravar no arquivo, se ele ainda não existir. Nesse caso, veja minha resposta abaixo, para uma versão que não sofre de condições de corrida.
11118 Dan Lenski
6
eu não vejo - o que há de errado com essa maneira fopen / fclose?
Johannes Schaub - litb 3/02/09
16
@ JohannesSchaub-litb: uma coisa errada com o método fopen()/ fclose()é que você talvez não consiga abrir um arquivo para leitura, mesmo que ele exista. Por exemplo, /dev/kmemexiste, mas a maioria dos processos não pode abri-lo nem para leitura. /etc/shadowé outro desses arquivos. Obviamente, ambos stat()e access()dependem de poder acessar o diretório que contém o arquivo; todas as apostas estão desativadas se você não puder fazer isso (sem permissão de execução no diretório que contém o arquivo).
Jonathan Leffler
1
if (file = fopen(fname, "r"))dará um aviso. Use parênteses ao redor da instrução dentro da instrução ifif ((file = fopen(fname, "r")))
Joakim

Respostas:

595

Procure a access()função, encontrada em unistd.h. Você pode substituir sua função por

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

Você também pode usar R_OK, W_OKe X_OKno lugar de F_OKverificar a permissão de leitura, a permissão de gravação e executar a permissão (respectivamente) em vez da existência, e você pode OU qualquer um deles juntos (por exemplo, verificar a permissão de leitura e gravação usando R_OK|W_OK)

Atualização : observe que, no Windows, você não pode W_OKtestar com confiabilidade a permissão de gravação, pois a função de acesso não leva em consideração as DACLs. access( fname, W_OK )pode retornar 0 (êxito) porque o arquivo não possui o atributo somente leitura definido, mas você ainda pode não ter permissão para gravar no arquivo.

Graeme Perrow
fonte
67
POSIX é um padrão ISO; define access (). C é outro padrão ISO; isso não.
31416 Jonathan Leffler
16
Existem armadilhas associadas ao access (). Existe uma janela de vulnerabilidade TOCTOU (hora da verificação, hora do uso) entre o uso de access () e o que mais você fizer depois. [... para ser continuado ...]
Jonathan Leffler
23
[... continuando ...] De maneira mais esotérica, nos sistemas POSIX, access () verifica se o UID real e o GID real, em vez do UID efetivo e do GID efetivo. Isso só importa para programas setuid ou setgid, mas importa intensamente, pois pode dar a resposta 'errada'.
Jonathan Leffler
3
Eu me deparei com essa pergunta ao procurar o motivo da access()quebra no meu código. Eu mudei do DevC ++ para o CodeBlocks e ele parou de funcionar. Então, não é infalível; +1 a mais para @Leffler.
Ben
11
Na maioria das vezes, sim (pode ser usado access()para verificar a existência de um arquivo), mas em um programa SUID ou SGID, mesmo isso pode estar incorreto. Se o arquivo testado estiver em um diretório que o UID real ou o GID real não puder acessar, access()poderá reportar esse arquivo quando ele existir. Esotérico e improvável? Sim.
Jonathan Leffler
116

Use statassim:

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

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

e chame assim:

#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;
}
codebunny
fonte
4
@LudvigANorin: nesses sistemas, as chances são de que access()também há problemas e há opções para criar access()e stat()trabalhar com arquivos grandes (maiores que 2 GB).
Jonathan Leffler
14
Algum de vocês pode apontar para a documentação referente à falha após 2 GB? Além disso, qual é a alternativa nesses casos?
Chamakits #
@JonathanLeffler Não statsofre a mesma vulnerabilidade de TOCTOU que access? (Não está claro para mim que seria melhor.)
Telêmaco
9
Ambos stat()e access()sofrem com a vulnerabilidade TOCTOU (o mesmo acontece lstat(), mas fstat()é seguro). Depende do que você fará com base na presença ou ausência do arquivo. Usar as opções corretas para open()geralmente é a melhor maneira de lidar com os problemas, mas pode ser complicado formular as opções corretas. Consulte também discussões sobre EAFP (mais fácil pedir perdão do que permissão) e LBYL (veja antes de pular) - consulte LBYL vs EAFP em Java , por exemplo.
Jonathan Leffler
87

Normalmente, quando você deseja verificar se um arquivo existe, é porque você deseja criar esse arquivo, se não existir. A resposta de Graeme Perrow é boa se você não deseja criar esse arquivo, mas é vulnerável a uma condição de corrida, se o fizer: outro processo pode criar o arquivo entre você, verificando se ele existe e abrindo-o para gravá-lo . (Não ria ... isso pode ter implicações ruins na segurança se o arquivo criado for um link simbólico!)

Se você deseja verificar a existência e criar o arquivo, se não existir, atomicamente , para que não haja condições de corrida, use o seguinte:

#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 */
}
Dan Lenski
fonte
8
Se você for usar O_CREAT, precisará fornecer o modo (permissões) como o terceiro argumento para abrir (). Considere também se deve ser usado O_TRUNC ou O_EXCL ou O_APPEND.
Jonathan Leffler
6
Jonathan Leffler está certo, este exemplo requer que O_EXCL funcione como está escrito.
Randy Proctor
6
Além disso, você precisa especificar o modo como um terceiro argumento: open (fechamento, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR)
Andrew Cooke
4
Note-se que isso é tão seguro quanto o sistema de arquivos é compatível com POSIX; em particular, as versões antigas do NFS têm a mesma condição de corrida que o O_EXCL deveria evitar! Existe uma solução alternativa, documentada open(2)(no Linux; as páginas de manual do seu sistema operacional podem variar), mas é bastante feia e pode não ser resistente a um invasor mal-intencionado.
Kevin
Observe que, para usar isso FILE*, é necessário usar o método posix fdopen(fd,"flags")para gerar umFILE*
Gem Taylor
32

Sim. Use stat(). Veja a página de manual para stat(2).

stat()falhará se o arquivo não existir, caso contrário, provavelmente será bem-sucedido. Se existir, mas você não tiver acesso de leitura ao diretório em que ele existe, também falhará, mas nesse caso qualquer método falhará (como você pode inspecionar o conteúdo de um diretório que talvez não seja exibido de acordo com os direitos de acesso? Simplesmente, você não pode).

Ah, como alguém mencionou, você também pode usar access(). No entanto, prefiro que stat(), como se o arquivo exista, ele imediatamente receba muitas informações úteis (quando foi atualizada pela última vez, qual o tamanho, o proprietário e / ou o grupo que possui o arquivo, as permissões de acesso etc.).

Mecki
fonte
5
o acesso é preferido se você precisar apenas saber se o arquivo existe. Stat () pode ter uma grande audição se você não precisar de todas as informações extras.
Martin Beckett
4
Na verdade, quando listo um diretório usando ls-command, ele chama stat para todos os arquivos presentes lá e que a execução de ls tem uma grande sobrecarga é uma novidade para mim. Na verdade, você pode executar ls em diretórios com milhares de arquivos e ele retorna em uma fração de segundo.
Mecki
2
@Mecki: stat possui uma sobrecarga adicional diferente de zero em comparação ao acesso em sistemas que suportam hardlinks. Isso ocorre porque o acesso precisa apenas olhar a entrada do diretório, enquanto o stat também precisa procurar o inode. Em dispositivos de armazenamento com tempo de busca ruim (por exemplo, fita), a diferença pode ser significativa, pois é improvável que a entrada e o inode do diretório estejam próximos um do outro.
Kevin
3
@Kevin A menos que você passe o F_OK para ele, access()verifica as permissões de acesso a um arquivo e elas são armazenadas no inode para esse arquivo e não estão em sua entrada de diretório (pelo menos para todos os sistemas de arquivos que possuem estruturas semelhantes a inode) . Então access()tem que acessar o inode exatamente da mesma maneira que stat()tem para acessá-lo. Portanto, o que você diz é válido apenas se você não verificar nenhuma permissão! E, na verdade, em alguns sistemas access()é implementado ainda por cima stat()(por exemplo, a glibc no GNU Hurd faz dessa maneira), portanto, não há garantia em primeiro lugar.
Mecki #
@Mecki: Quem disse algo sobre a verificação de permissões? Eu estava falando especificamente sobre o F_OK. E sim, alguns sistemas são mal implementados. O acesso será pelo menos tão rápido quanto o stat em todos os casos e poderá ser mais rápido algumas vezes.
Kevin
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
        }
mesutpiskin
fonte
1
Isso não causaria um vazamento de memória? Você nunca fecha o arquivo, se existir.
precisa saber é o seguinte
1
Esse é um método simples e bom. Se você estiver no Windows MSVC, use-o: (fopen_s(file, "sample.txt", "r"))uma vez que fopen()é considerado obsoleto (ou desative erros obsoletos, mas isso não é recomendado).
Nikos
15
fopen()é padrão C, não vai a lugar nenhum. É apenas "reprovado" pela Microsoft. Não use a fopen_s()menos que queira um código não portátil e específico da plataforma.
Andrew Henle
Chamar fclose () para nada? Necessário atribuir a variável 'arquivo' primeiro!
Jenix
1
A variável 'arquivo' aqui tem um valor de lixo. Por que se preocupar em fechá-lo em primeiro lugar? Você está apenas chamando 'fclose (SOME_RANDOM_ADDRESS);' ..
Jenix
6

Com a ajuda do Visual C ++, eu costumava usar

/* 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" );
   }
}

Também vale a pena notar os valores do modo de :_access(const char *path,int mode)

  • 00: Apenas existência

  • 02: permissão de gravação

  • 04: permissão de leitura

  • 06: permissão de leitura e gravação

Como você fopenpode falhar em situações em que o arquivo existe, mas não pode ser aberto conforme solicitado.

Edit: Basta ler a publicação de Mecki. stat()parece um caminho mais arrumado. Ho hum.

SmacL
fonte
o acesso é preferido se você precisar apenas saber se o arquivo existe. Stat () pode ter um grande número de ouvidos.
22668 Martin Beckett
4

Você pode usar a função realpath ().

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

Eu acho que a função access () , que é encontrada em, unistd.hé uma boa opção para Linux(você também pode usar o stat ).

Você pode usá-lo assim:

#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);
    }
}

E você obtém a seguinte saída:

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
Michi
fonte