Qual è il modo migliore per verificare se esiste un file in C?


436

C'è un modo migliore del semplice tentativo di aprire il file?

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

Penso che darò la risposta al metodo di accesso, nonostante il metodo stat sia un'alternativa molto ragionevole, l'accesso fa il lavoro.
Dave Marshall,

1
Vuoi davvero verificare l'esistenza? Oppure vuoi controllare e scrivere nel file se non esiste già. In tal caso, vedere la mia risposta di seguito, per una versione che non soffre di condizioni di gara.
Dan Lenski,

6
non vedo - cosa c'è che non va in quel modo fopen / fclose?
Johannes Schaub - 2

16
@ JohannesSchaub-litb: una cosa che non va nel metodo fopen()/ fclose()è che potresti non essere in grado di aprire un file per la lettura anche se esiste. Ad esempio, /dev/kmemesiste, ma la maggior parte dei processi non può aprirlo nemmeno per la lettura. /etc/shadowè un altro file del genere. Naturalmente, entrambi stat()e si access()affidano alla possibilità di accedere alla directory contenente il file; tutte le scommesse sono disattivate se non puoi farlo (nessuna autorizzazione di esecuzione sulla directory contenente il file).
Jonathan Leffler,

1
if (file = fopen(fname, "r"))darà un avvertimento. Usa la parentesi attorno all'istruzione all'interno dell'istruzione ifif ((file = fopen(fname, "r")))
Joakim,

Risposte:


595

Cerca la access()funzione, trovata in unistd.h. Puoi sostituire la tua funzione con

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

Puoi anche usare R_OK, W_OKe X_OKal posto di F_OKverificare l'autorizzazione alla lettura, l'autorizzazione alla scrittura ed eseguire l'autorizzazione (rispettivamente) anziché l'esistenza, e puoi OPPURE una di esse insieme (cioè verifica l' autorizzazione alla lettura e alla scrittura usando R_OK|W_OK)

Aggiornamento : si noti che su Windows, non è possibile utilizzare W_OKper testare in modo affidabile l'autorizzazione alla scrittura, poiché la funzione di accesso non tiene conto dei DACL. access( fname, W_OK )potrebbe restituire 0 (esito positivo) perché il file non ha l'attributo di sola lettura impostato, ma potresti non disporre dell'autorizzazione per scrivere nel file.


67
POSIX è uno standard ISO; definisce access (). C è un altro standard ISO; non è così.
Jonathan Leffler,

16
Ci sono insidie ​​associate ad access (). Esiste una finestra di vulnerabilità TOCTOU (tempo di controllo, tempo di utilizzo) tra l'utilizzo di access () e qualsiasi altra operazione successiva. [... continua ...]
Jonathan Leffler,

23
[... continua ...] Piuttosto più esotericamente, sui sistemi POSIX, access () verifica se l'UID reale e GID reale, piuttosto che l'UID effettivo e GID effettivo. Ciò importa solo per i programmi setuid o setgid, ma poi conta intensamente in quanto potrebbe dare la risposta "sbagliata".
Jonathan Leffler,

3
Mi sono imbattuto in questa domanda quando ho cercato il motivo access()per cui ho rotto il mio codice. Sono passato da DevC ++ a CodeBlocks e ha smesso di funzionare. Quindi, non è infallibile; +1 in più a @Leffler.
Ben

11
La maggior parte delle volte, sì (è OK per access()verificare l'esistenza di un file), ma in un programma SUID o SGID, anche quello potrebbe essere errato. Se il file testato si trova in una directory alla quale l'UID reale o il GID reale non possono accedere, access()potrebbe non essere segnalato tale file quando esiste. Esoterico e improbabile? Sì.
Jonathan Leffler,

116

Usa statcosì:

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

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

e chiamalo così:

#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: su tali sistemi, è probabile che ci siano access()anche problemi e ci sono opzioni da usare per creare access()e stat()lavorare con file di grandi dimensioni (più grandi di 2 GB).
Jonathan Leffler,

14
Qualcuno di voi potrebbe indicare la documentazione relativa all'errore dopo 2 GB? Inoltre, qual è l'alternativa in questi casi?
chamakits il

@JonathanLeffler Non statsoffre della stessa vulnerabilità TOCTOU di access? (Non mi è chiaro che sarebbe meglio.)
Telemaco,

9
Entrambi stat()e access()soffrono della vulnerabilità TOCTOU (così fa lstat(), ma fstat()è sicuro). Dipende da cosa hai intenzione di fare in base alla presenza o all'assenza del file. L'uso delle opzioni corrette per di open()solito è il modo migliore per affrontare i problemi, ma può essere complicato formulare le opzioni giuste. Vedi anche discussioni su EAFP (Più facile chiedere perdono che autorizzazione) e LBYL (Look Before You Leap) - vedi LBYL vs EAFP in Java , per esempio.
Jonathan Leffler,

87

Di solito, quando si desidera verificare l'esistenza di un file, è perché si desidera creare quel file in caso contrario. La risposta di Graeme Perrow è buona se non vuoi creare quel file, ma è vulnerabile a una condizione di competizione se lo fai: un altro processo potrebbe creare il file tra te controllando se esiste e lo stai effettivamente aprendo per scriverlo . (Non ridere ... questo potrebbe avere conseguenze negative sulla sicurezza se il file creato fosse un link simbolico!)

Se vuoi verificare l'esistenza e creare il file se non esiste, atomicamente in modo che non ci siano condizioni di competizione, usa questo:

#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
Se stai per usare O_CREAT, devi fornire la modalità (permessi) come terzo argomento da aprire (). Valuta anche se utilizzare O_TRUNC o O_EXCL o O_APPEND.
Jonathan Leffler,

6
Jonathan Leffler ha ragione, questo esempio richiede che O_EXCL funzioni come scritto.
Randy Proctor,

6
Inoltre, è necessario specificare la modalità come terzo argomento: open (blocco, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR)
andrew cooke

4
Va notato che questo è sicuro tanto quanto il filesystem è conforme a POSIX; in particolare, le vecchie versioni di NFS hanno le stesse condizioni di gara che O_EXCL avrebbe dovuto evitare! C'è una soluzione alternativa, documentata in open(2)(su Linux; le pagine man del tuo sistema operativo possono variare), ma è piuttosto brutta e potrebbe non essere resistente a un malintenzionato malintenzionato.
Kevin,

Si noti che per usarlo con FILE*, è quindi necessario utilizzare il metodo posix fdopen(fd,"flags")per generare unFILE*
Gem Taylor

32

Sì. Usa stat(). Vedi la pagina man per stat(2).

stat()fallirà se il file non esiste, altrimenti molto probabilmente avrà successo. Se esiste, ma non hai accesso in lettura alla directory in cui esiste, fallirà anche, ma in tal caso qualsiasi metodo fallirà (come puoi ispezionare il contenuto di una directory che potresti non vedere in base ai diritti di accesso? Semplicemente, non puoi).

Oh, come ha detto qualcun altro, puoi anche usare access(). Tuttavia preferisco stat(), come se il file esista mi ottenga immediatamente molte informazioni utili (quando è stato aggiornato l'ultima volta, quanto è grande, il proprietario e / o il gruppo che possiede il file, le autorizzazioni di accesso e così via).


5
l'accesso è preferibile se devi solo sapere se il file esiste. Stat () può avere un grande ascolto se non hai bisogno di tutte le informazioni extra.
Martin Beckett,

4
In realtà quando elenco una directory usando ls-command, chiama stat per ogni file presente e che l'esecuzione di ls ha un grande sovraccarico è abbastanza nuovo per me. In realtà è possibile eseguire ls su directory con migliaia di file e restituisce in una frazione di secondo.
Mecki,

2
@Mecki: stat ha un sovraccarico aggiuntivo diverso da zero rispetto all'accesso su sistemi che supportano hardlink. Questo perché l'accesso deve solo guardare la voce della directory, mentre stat deve cercare anche l'inode. Sui dispositivi di archiviazione con tempi di ricerca errati (ad es. Nastro), la differenza può essere significativa poiché è improbabile che la voce della directory e l'inode siano uno accanto all'altro.
Kevin

3
@Kevin A meno che non gli passi solo F_OK, access()controlla le autorizzazioni di accesso ai file di un file e queste sono memorizzate nell'inode per quel file e non si trovano nella sua voce di directory (almeno per tutti i file system che hanno strutture simili agli inode) . Quindi access()deve accedere all'inode esattamente allo stesso modo in cui stat()deve accedervi. Quindi ciò che dici vale solo se non controlli alcuna autorizzazione! E in realtà su alcuni sistemi access()viene addirittura implementato stat()(ad esempio, glibc su GNU Hurd lo fa in questo modo), quindi non vi è alcuna garanzia in primo luogo.
Mecki,

@Mecki: chi ha detto qualcosa sul controllo dei permessi? Stavo parlando in particolare di F_OK. E sì, alcuni sistemi sono implementati male. L'accesso sarà almeno veloce come stat in ogni caso e potrebbe essere più veloce in alcuni casi.
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
        }

1
Ciò non causerebbe una perdita di memoria? Non chiudere mai il file se esiste.
LegionMammal978,

1
Questo è un metodo buono e semplice. Se si è in Windows MSVC, utilizzare questo invece: (fopen_s(file, "sample.txt", "r"))poiché fopen()è considerato deprecato (o disabilitare errori ritirate, ma non è raccomandato).
Nikos,

15
fopen()è standard C, non va da nessuna parte. È solo "deprecato" da Microsoft. Non utilizzare fopen_s()se non si desidera un codice non portatile specifico per la piattaforma.
Andrew Henle

Chiamare fclose () per niente? Necessario assegnare prima la variabile 'file'!
Jenix,

1
La variabile 'file' qui ha un valore di immondizia. Perché preoccuparsi di chiuderlo in primo luogo? Stai solo chiamando 'fclose (SOME_RANDOM_ADDRESS);' ..
Jenix,

6

Con l'aiuto di Visual C ++, tenderei ad andare avanti

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

Vale anche la pena notare i valori della modalità di :_access(const char *path,int mode)

  • 00: Esistenza solo

  • 02: permesso di scrittura

  • 04: permesso di lettura

  • 06: permesso di lettura e scrittura

Poiché fopenpotresti non riuscire nelle situazioni in cui il file esisteva ma non poteva essere aperto come richiesto.

Modifica: basta leggere il post di Mecki. stat()sembra un modo più ordinato di andare. Ho hum.


l'accesso è preferibile se devi solo sapere se il file esiste. Stat () può avere un grande ascolto.
Martin Beckett,

4

È possibile utilizzare la funzione realpath ().

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

3

Penso che la funzione access () , che si trova in, unistd.hsia una buona scelta per Linux(puoi usare anche stat ).

Puoi usarlo in questo modo:

#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 ottieni il seguente output:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.