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

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.


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

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 */
}

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.).


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
        }

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.


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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.