Jaki jest najlepszy sposób sprawdzenia, czy plik istnieje w C?

436

Czy istnieje lepszy sposób niż próba otwarcia pliku?

int exists(const char *fname)
{
    FILE *file;
    if ((file = fopen(fname, "r")))
    {
        fclose(file);
        return 1;
    }
    return 0;
}
Dave Marshall
źródło
Myślę, że dam odpowiedź na metodę dostępu, mimo że metoda stat jest bardzo rozsądną alternatywą, dostęp wykonuje zadanie.
Dave Marshall
1
Czy naprawdę chcesz po prostu sprawdzić istnienie? A może chcesz to sprawdzić i napisać do pliku, jeśli jeszcze nie istnieje. Jeśli tak, zobacz moją odpowiedź poniżej, aby uzyskać wersję, która nie cierpi z powodu warunków wyścigowych.
Dan Lenski
6
nie widzę - co jest nie tak z tym sposobem fopen / fclose?
Johannes Schaub - litb
16
@ JohannesSchaub-litb: jedną z wad metody fopen()/ fclose()jest to, że możesz nie być w stanie otworzyć pliku do odczytu, nawet jeśli on istnieje. Na przykład /dev/kmemistnieje, ale większość procesów nie może go otworzyć nawet do odczytu. /etc/shadowto kolejny taki plik. Oczywiście zarówno stat()i access()polegają na możliwości uzyskania dostępu do katalogu zawierającego plik; wszystkie zakłady są wyłączone, jeśli nie możesz tego zrobić (brak uprawnień do wykonania w katalogu zawierającym plik).
Jonathan Leffler
1
if (file = fopen(fname, "r"))da ostrzeżenie. Użyj nawiasów wokół instrukcji wewnątrz instrukcji ifif ((file = fopen(fname, "r")))
Joakim

Odpowiedzi:

595

Wyszukaj access()funkcję znajdującą się w unistd.h. Możesz zamienić swoją funkcję na

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

Możesz również użyć R_OK, W_OKa X_OKzamiast F_OKsprawdzać uprawnienia do odczytu, uprawnienia do zapisu i wykonać uprawnienia (odpowiednio) zamiast istnienia, i możesz LUB dowolne z nich razem (tj. Sprawdzić zarówno uprawnienia do odczytu, jak i zapisu za pomocą R_OK|W_OK)

Aktualizacja : należy pamiętać, że w systemie Windows nie można używać W_OKdo niezawodnego testowania uprawnień do zapisu, ponieważ funkcja dostępu nie uwzględnia list DACL. access( fname, W_OK )może zwrócić 0 (sukces), ponieważ plik nie ma ustawionego atrybutu tylko do odczytu, ale nadal możesz nie mieć uprawnień do zapisu do pliku.

Graeme Perrow
źródło
67
POSIX jest standardem ISO; definiuje dostęp (). C to kolejny standard ISO; to nie.
Jonathan Leffler
16
Istnieją pułapki związane z dostępem (). Istnieje okno TOCTOU (czas sprawdzenia, czas użycia) podatności między użyciem access () a wszystkim innym, co zrobisz później. [... ciąg dalszy nastąpi ...]
Jonathan Leffler
23
[... kontynuuje ...] Raczej bardziej ezoterycznie, w systemach POSIX, access () sprawdza, czy rzeczywisty UID i prawdziwy GID, a nie efektywny UID i efektywny GID. Ma to znaczenie tylko dla programów setuid lub setgid, ale ma duże znaczenie, ponieważ może dać „złą” odpowiedź.
Jonathan Leffler
3
Natknąłem się na to pytanie, gdy szukałem przyczyny access()włamania do mojego kodu. Przeprowadziłem się z DevC ++ do CodeBlocks i przestało działać. Więc to nie jest nieomylne; +1 więcej dla @Leffler.
Ben
11
access()Przez większość czasu tak (można sprawdzać istnienie pliku), ale w programie SUID lub SGID nawet to może być niepoprawne. Jeśli testowany plik znajduje się w katalogu, do którego prawdziwy UID lub prawdziwy GID nie ma dostępu, access()może nie zgłosić żadnego takiego pliku, jeśli taki istnieje. Ezoteryczny i nieprawdopodobny? Tak.
Jonathan Leffler,
116

Użyj w statten sposób:

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

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

i nazwij to tak:

#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
źródło
4
@LudvigANorin: w takich systemach są access()również szanse, że występują problemy, i istnieją opcje do tworzenia access()i stat()pracy z dużymi plikami (większymi niż 2 GB).
Jonathan Leffler
14
Czy któryś z was mógłby wskazać dokumentację dotyczącą awarii po 2 GB? Jaka jest alternatywa w takich przypadkach?
chamakits,
@JonathanLeffler Nie statcierpi z powodu tej samej podatności na zagrożenia TOCTOU co access? (Nie jest dla mnie jasne, że byłoby lepiej.)
Telemachus
9
Zarówno podatność na lukę w TOCTOU, jak stat()i na nią access()cierpi (tak lstat(), ale fstat()jest bezpieczna). To zależy od tego, co będziesz robić, w zależności od obecności lub braku pliku. Użycie prawidłowych opcji open()jest zwykle najlepszym sposobem radzenia sobie z problemami, ale sformułowanie właściwych opcji może być trudne. Zobacz także dyskusje na temat EAFP (łatwiej prosić o przebaczenie niż pozwolenie) i LBYL (Look Before You Leap) - patrz na przykład LBYL vs EAFP w Javie .
Jonathan Leffler
87

Zwykle, gdy chcesz sprawdzić, czy plik istnieje, to dlatego, że chcesz go utworzyć , jeśli nie istnieje. Odpowiedź Graeme Perrow jest dobra, jeśli nie chcesz utworzyć tego pliku, ale jeśli to zrobisz , jest podatny na warunki wyścigu: inny proces może utworzyć plik między tobą, sprawdzając, czy istnieje, a faktycznie otwierając go, aby do niego napisać . (Nie śmiej się ... może to mieć zły wpływ na bezpieczeństwo, jeśli utworzony plik był dowiązaniem symbolicznym!)

Jeśli chcesz sprawdzić istnienie i utworzyć plik, jeśli nie istnieje, atomowo , aby nie było warunków wyścigu, użyj tego:

#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
źródło
8
Jeśli zamierzasz używać O_CREAT, musisz podać tryb (uprawnienia) jako trzeci argument otwarcia (). Zastanów się także, czy należy zastosować O_TRUNC, O_EXCL lub O_APPEND.
Jonathan Leffler
6
Jonathan Leffler ma rację, ten przykład wymaga O_EXCL do pracy zgodnie z opisem.
Randy Proctor
6
Ponadto musisz określić tryb jako trzeci argument: open (lock, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR)
andrew cooke
4
Należy zauważyć, że jest to tak bezpieczne, jak system plików jest zgodny z POSIX; w szczególności, stare wersje NFS mają warunki wyścigowe, których O_EXCL miał unikać! Istnieje obejście, udokumentowane w open(2)(w systemie Linux; strony podręcznika systemu operacyjnego mogą się różnić), ale jest raczej brzydkie i może nie być odporne na złośliwego atakującego.
Kevin
Zauważ, że aby tego użyć FILE*, musisz użyć metody posix, fdopen(fd,"flags")aby wygenerowaćFILE*
Gem Taylor
32

Tak. Zastosowanie stat(). Zobacz stronę podręcznika dla stat(2).

stat()zawiedzie, jeśli plik nie istnieje, w przeciwnym razie najprawdopodobniej się uda. Jeśli istnieje, ale nie masz dostępu do odczytu katalogu, w którym on istnieje, również się nie powiedzie, ale w takim przypadku żadna metoda zawiedzie (w jaki sposób możesz sprawdzić zawartość katalogu, którego możesz nie zobaczyć zgodnie z prawami dostępu? Po prostu nie możesz).

Och, jak ktoś wspomniał, możesz także użyć access() . Jednak wolę stat(), jak gdyby plik istniał, natychmiast dostarczy mi wiele użytecznych informacji (kiedy był ostatnio aktualizowany, jak duży jest, właściciel i / lub grupa, która jest właścicielem pliku, uprawnienia dostępu itp.).

Mecki
źródło
5
dostęp jest preferowany, jeśli potrzebujesz tylko wiedzieć, czy plik istnieje. Stat () może być podsłuchany, jeśli nie potrzebujesz wszystkich dodatkowych informacji.
Martin Beckett,
4
Właściwie, kiedy wymieniam katalog za pomocą polecenia ls, wywołuje on stat dla każdego pliku, który tam jest, a uruchomienie ls ma duży narzut, co jest dla mnie całkiem nowe. W rzeczywistości możesz uruchomić ls w katalogach zawierających tysiące plików, a on zwraca w ułamku sekundy.
Mecki,
2
@Mecki: stat ma niezerowy dodatkowy narzut w porównaniu z dostępem w systemach obsługujących dowiązania twarde. Jest tak, ponieważ dostęp musi tylko patrzeć na pozycję katalogu, podczas gdy stat musi także sprawdzać i-węzeł. Na urządzeniach magazynujących o złym czasie przeszukiwania (np. Taśma) różnica może być znacząca, ponieważ wpis katalogu i i-węzeł raczej nie znajdą się obok siebie.
Kevin
3
@Kevin O ile nie przekazujesz do niego tylko F_OK, access()sprawdza on uprawnienia dostępu do pliku, które są przechowywane w i-węźle dla tego pliku i nie znajdują się we wpisie katalogu (przynajmniej dla wszystkich systemów plików, które mają struktury podobne do i-węzłów) . access()Musi więc uzyskać dostęp do i-węzła dokładnie w taki sam sposób, jak stat()ma do niego dostęp. To, co mówisz, ma zastosowanie tylko wtedy, gdy nie sprawdzasz żadnych uprawnień! W rzeczywistości na niektórych systemach access()jest nawet zaimplementowany stat()(np. Glibc na GNU Hurd robi to w ten sposób), więc nie ma żadnej gwarancji.
Mecki
@Mecki: Kto powiedział coś o sprawdzaniu uprawnień? Mówiłem konkretnie o F_OK. I tak, niektóre systemy są źle wdrożone. Dostęp będzie w każdym przypadku co najmniej tak szybki jak statystyka i przez pewien czas może być szybszy.
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
źródło
1
Czy nie spowodowałoby to wycieku pamięci? Nigdy nie zamykasz pliku, jeśli istnieje.
LegionMammal978
1
To dobra, prosta metoda. Jeśli korzystasz z systemu Windows MSVC, użyj tego: (fopen_s(file, "sample.txt", "r"))ponieważ fopen()jest to uznawane za przestarzałe (lub wyłącz przestarzałe błędy, ale nie jest to zalecane).
Nikos
15
fopen()jest standardem C, nigdzie się nie wybiera. Jest to „przestarzałe” przez Microsoft. Nie używaj, fopen_s()chyba że chcesz mieć nieprzenośny kod specyficzny dla platformy.
Andrew Henle,
Wywoływanie fclose () za darmo? Najpierw trzeba przypisać zmienną „plik”!
Jenix
1
Zmienna „plik” ma tutaj wartość śmieci. Po co zawracać sobie głowę zamykaniem go? Dzwonisz po prostu „fclose (SOME_RANDOM_ADDRESS);” ..
Jenix
6

Z pomocy Visual C ++ zwykle bym się przyłączył

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

Warto również zwrócić uwagę na wartości trybu :_access(const char *path,int mode)

  • 00: tylko istnienie

  • 02: Napisz pozwolenie

  • 04: Czytaj zezwolenie

  • 06: Pozwolenie na odczyt i zapis

Ponieważ twój fopenmoże zawieść w sytuacjach, w których plik istniał, ale nie można go otworzyć zgodnie z żądaniem.

Edycja: wystarczy przeczytać post Mecki. stat()wygląda na lepszy sposób. Ho hum.

SmacL
źródło
dostęp jest preferowany, jeśli potrzebujesz tylko wiedzieć, czy plik istnieje. Stat () może być podsłuchany.
Martin Beckett
4

Możesz użyć funkcji realpath ().

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

Myślę, że funkcja access () , którą można znaleźć, unistd.hjest dobrym wyborem dla Linux(możesz także użyć statystyki ).

Możesz użyć tego w następujący sposób:

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

Otrzymujesz następujące dane wyjściowe:

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
źródło