Який найкращий спосіб перевірити, чи існує файл на C?


436

Чи є кращий спосіб, ніж просто намагатися відкрити файл?

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

Думаю, я дам відповідь на метод доступу, незважаючи на те, що метод stat є дуже розумною альтернативою, доступ отримує роботу.
Дейв Маршалл

1
Ви справді просто хочете перевірити наявність? Або ви хочете перевірити, і запишіть у файл, якщо він ще не існує. Якщо так, дивіться мою відповідь нижче для версії, яка не страждає від перегонових умов.
Дан Ленськи

6
я не бачу - що з цим фопенд / fclose спосіб не так?
Йоханнес Шауб - ліб

16
@ JohannesSchaub-litb: одне, що не так з методом fopen()/, fclose()це те, що ви, можливо, не зможете відкрити файл для читання, навіть якщо він існує. Наприклад, /dev/kmemіснує, але більшість процесів не може відкрити його навіть для читання. /etc/shadowє ще один такий файл. Звичайно, обидва stat()і access()покладаються на можливість доступу до каталогу, що містить файл; всі ставки знімаються, якщо ви цього не можете зробити (жодного дозволу на виконання каталогу, що містить файл).
Джонатан Леффлер

1
if (file = fopen(fname, "r"))дасть попередження. Використовуйте круглі дужки навколо оператора if (if)if ((file = fopen(fname, "r")))
Йоаким

Відповіді:


595

Знайдіть access()функцію, знайдену в unistd.h. Ви можете замінити свою функцію на

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

Ви також можете використовувати R_OK, W_OKі X_OKзамість того, F_OKщоб перевірити на дозвіл читання, дозвіл на написання дозволу та виконати дозвіл (відповідно), а не існування, і ви можете АБО будь-який з них разом (тобто перевірити наявність дозволу читання та запису за допомогою R_OK|W_OK)

Оновлення : Зауважте, що в Windows не W_OKможна надійно перевірити дозвіл на запис, оскільки функція доступу не враховує DACL. access( fname, W_OK )може повернути 0 (успіх), оскільки файл не має атрибута лише для читання, але ви все одно не можете мати дозвіл на запис у файл.


67
POSIX - стандарт ISO; він визначає доступ (). C - ще один стандарт ISO; це не.
Джонатан Леффлер

16
Є підводні камені, пов’язані з доступом (). Існує вікно вразливості між TOCTOU (час перевірки, час використання) між використанням доступу () і тим, що ви робите після цього. [... буде продовжено ...]
Джонатан Леффлер

23
[... продовження ...] У системах POSIX швидше езотерично, доступ () перевіряє, чи справжній UID та справжній GID, а не ефективний UID та ефективний GID. Це має значення лише для встановлених або встановлених програм, але це важливо, оскільки це може дати "неправильну" відповідь.
Джонатан Леффлер

3
Я зіткнувся з цим питанням, коли шукав причину, яку access()зламав у моєму коді. Я перейшов з DevC ++ до CodeBlocks, і він перестав працювати. Отже, це не безпомилково; Ще +1 для @Leffler
Бен

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 Гб).
Джонатан Леффлер

14
Чи може хтось із вас вказати на документацію щодо несправності після 2 ГБ? Також, яка альтернатива в таких випадках?
chamakits

@JonathanLeffler statНе страждає від тієї ж вразливості TOCTOU, як access? (Мені не зрозуміло, що було б краще.)
Телемах

9
І вони, stat()і access()страждають від вразливості TOCTOU (так і є lstat(), але fstat()в безпеці). Це залежить від того, що ви збираєтеся робити, залежно від наявності чи відсутності файлу. Використання правильних варіантів для, open()як правило, є найкращим способом вирішення проблем, але це може бути складно сформулювати правильні варіанти. Дивіться також дискусії щодо EAFP (простіше просити прощення, ніж дозволу) та LBYL (Подивіться, перш ніж вистрибнути ) - див., Наприклад, LBYL vs EAFP на Java .
Джонатан Леффлер

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, вам потрібно надати режим (дозволи) як третій аргумент для відкриття (). Також врахуйте, чи слід використовувати O_TRUNC або O_EXCL або O_APPEND.
Джонатан Леффлер

6
Джонатан Леффлер має рацію, цей приклад вимагає, щоб O_EXCL працював як написано.
Ренді Проктор

6
Також потрібно вказати режим як третій аргумент: відкрити (замок, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR)
andrew cooke

4
Слід зазначити, що це так само безпечно, оскільки файлова система сумісна з POSIX; зокрема, старі версії NFS мають той самий гоночний стан, який O_EXCL повинен був уникати! Існує рішення, задокументоване в open(2)(на Linux; сторінки сторінки вашої ОС можуть відрізнятися), але це досить некрасиво і не може бути стійким до зловмисного зловмисника.
Кевін

Зауважте, що для використання цього FILE*вам потрібно використовувати метод posix fdopen(fd,"flags")для створенняFILE*
Gem Taylor

32

Так. Використовуйте stat(). Дивіться сторінку чоловіка для stat(2).

stat()не вдасться, якщо файл не існує, інакше, швидше за все, успіх. Якщо він існує, але у вас немає доступу для читання до каталогу, де він існує, він також вийде з ладу, але в такому випадку будь-який метод вийде з ладу (як ви можете перевірити вміст каталогу, який ви можете не бачити відповідно до прав доступу? Просто не можна).

О, як хтось ще згадував, ви також можете використовувати access(). Однак я вважаю за краще stat(), так як якщо файл існує, він одразу отримає мені багато корисної інформації (коли він востаннє оновлювався, наскільки він великий, власник та / або група, яка володіє файлом, права доступу тощо).


5
доступ надається, якщо вам потрібно знати, чи існує файл. Stat () може мати велике підслуховування, якщо вам не потрібна вся додаткова інформація.
Мартін Беккет

4
Насправді, коли я перераховую каталог за допомогою ls-command, він викликає stat для кожного файлу, який там присутній, і те, що у запущеного ls є великі накладні дані, для мене досить нове. Насправді ви можете запускати ls в каталогах з тисячами файлів, і він повертається за частку секунди.
Mecki

2
@Mecki: stat має додаткові нульові накладні витрати порівняно з доступом до систем, що підтримують жорсткі посилання. Це пояснюється тим, що для доступу потрібно дивитись лише запис у каталозі, а stat також шукати індед. На пристроях зберігання з поганим часом пошуку (наприклад, стрічка) різниця може бути істотною, оскільки вхід у каталог та inode навряд чи будуть поруч.
Кевін

3
@Kevin Якщо ви тільки не передаєте йому F_OK, він access()перевіряє дозволи на доступ до файлу, і вони зберігаються в inode для цього файлу і не містяться в його каталозі (принаймні для всіх файлових систем, що мають структури, подібні до inode) . Таким чином, access()має отримати доступ до inode точно так само, stat()як і до неї. Отже, те, що ви говорите, справедливо лише в тому випадку, якщо ви не перевіряєте на наявність дозволів! А насправді на деяких системах access()навіть впроваджено поверх stat()(наприклад, glibc на GNU Hurd робить це так), тому гарантії в першу чергу немає.
Mecki

@Mecki: Хто сказав щось про перевірку дозволів? Я спеціально говорив про F_OK. І так, деякі системи погано реалізовані. Доступ буде принаймні таким же швидким, як статист, у кожному випадку, а може бути швидшим і деякий час.
Кевін

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

1
Це хороший, простий метод. Якщо ви перебуваєте в Windows MSVC, використовуйте це замість: (fopen_s(file, "sample.txt", "r"))оскільки fopen()вважається застарілим (або вимкніть застарілі помилки, але це не рекомендується).
Нікос

15
fopen()є стандартним С, нікуди не дінеться. Це лише "застарілий" Microsoft. Не використовуйте, fopen_s()якщо вам не потрібний не портативний код, орієнтований на платформу.
Ендрю Генле

Викликати fclose () дарма? Потрібно спочатку призначити змінну 'файл'!
Дженікс

1
Змінна "файл" тут має значення сміття. Чому в першу чергу турбуватися закривати його? Ви просто дзвоните "fclose (SOME_RANDOM_ADDRESS);" ..
Дженікс

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 () може мати велике підслуховування.
Мартін Бекетт

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.