Apa cara terbaik untuk memeriksa apakah ada file di C?

436

Apakah ada cara yang lebih baik daripada sekadar mencoba membuka file?

int exists(const char *fname)
{
    FILE *file;
    if ((file = fopen(fname, "r")))
    {
        fclose(file);
        return 1;
    }
    return 0;
}
Dave Marshall
sumber
Saya pikir saya akan memberikan jawaban untuk metode akses, meskipun metode stat menjadi alternatif yang sangat masuk akal, akses menyelesaikan pekerjaan.
Dave Marshall
1
Apakah Anda benar - benar hanya ingin memeriksa keberadaan? Atau apakah Anda ingin memeriksa, dan menulis ke file jika belum ada. Jika demikian, lihat jawaban saya di bawah ini, untuk versi yang tidak menderita kondisi balapan.
Dan Lenski
6
saya tidak melihat - apa yang salah dengan cara fopen / fclose itu?
Johannes Schaub - litb
16
@ JohannesSchaub-litb: satu hal yang salah dengan metode fopen()/ fclose()adalah bahwa Anda mungkin tidak dapat membuka file untuk dibaca meskipun sudah ada. Misalnya, /dev/kmemada, tetapi sebagian besar proses tidak dapat membukanya bahkan untuk membaca. /etc/shadowadalah file lain seperti itu. Tentu saja, keduanya stat()dan access()bergantung pada dapat mengakses direktori yang berisi file; semua taruhan dibatalkan jika Anda tidak bisa melakukan itu (tidak ada izin eksekusi pada direktori yang berisi file).
Jonathan Leffler
1
if (file = fopen(fname, "r"))akan memberi peringatan. Gunakan tanda kurung di sekitar pernyataan di dalam pernyataan ifif ((file = fopen(fname, "r")))
Joakim

Jawaban:

595

Cari access()fungsinya, ditemukan di unistd.h. Anda dapat mengganti fungsi Anda dengan

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

Anda juga dapat menggunakan R_OK,, W_OKdan X_OKsebagai pengganti F_OKuntuk memeriksa izin baca, menulis izin, dan mengeksekusi izin (masing-masing) daripada keberadaan, dan Anda dapat ATAU salah satu dari mereka bersama-sama (yaitu memeriksa baik membaca dan menulis menggunakan izin R_OK|W_OK)

Pembaruan : Perhatikan bahwa pada Windows, Anda tidak dapat menggunakan W_OKuntuk menguji izin menulis dengan andal, karena fungsi akses tidak memperhitungkan DACL. access( fname, W_OK )dapat mengembalikan 0 (berhasil) karena file tidak memiliki set atribut read-only, tetapi Anda masih mungkin tidak memiliki izin untuk menulis ke file.

Graeme Perrow
sumber
67
POSIX adalah standar ISO; ini mendefinisikan akses (). C adalah standar ISO lainnya; itu tidak.
Jonathan Leffler
16
Ada jebakan yang terkait dengan akses (). Ada jendela kerentanan TOCTOU (waktu pemeriksaan, waktu penggunaan) antara menggunakan akses () dan apa pun yang Anda lakukan sesudahnya. [... diteruskan ...]
Jonathan Leffler
23
[... melanjutkan ...] Agak lebih esoteris, pada sistem POSIX, akses () memeriksa apakah UID asli dan GID nyata, daripada UID efektif dan GID efektif. Ini hanya penting untuk program setuid atau setgid, tetapi kemudian sangat penting karena dapat memberikan jawaban yang 'salah'.
Jonathan Leffler
3
Saya menemukan pertanyaan ini ketika mencari alasan untuk access()memecahkan kode saya. Saya pindah dari DevC ++ ke CodeBlocks dan berhenti bekerja. Jadi, itu tidak sempurna; +1 lebih banyak ke @Leffler.
Ben
11
Sebagian besar waktu, ya ( access()boleh digunakan untuk memeriksa keberadaan file), tetapi dalam program SUID atau SGID, bahkan itu bisa saja salah. Jika file yang diuji berada dalam direktori yang UID atau GID nyata tidak dapat mengakses, access()mungkin tidak melaporkan file seperti itu ketika memang ada. Esoteris dan mustahil? Iya.
Jonathan Leffler
116

Gunakan statseperti ini:

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

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

dan menyebutnya seperti ini:

#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
sumber
4
@LudvigANorin: pada sistem seperti itu, kemungkinannya access()juga ada masalah, dan ada opsi untuk digunakan untuk membuat access()dan stat()bekerja dengan file besar (lebih besar dari 2 GB).
Jonathan Leffler
14
Bisakah Anda menunjuk dokumentasi tentang kegagalan setelah 2 GB? Juga, apa alternatif dalam kasus seperti itu?
chamakits
@JonathanLeffler Tidak statmenderita kerentanan TOCTOU yang sama access? (Tidak jelas bagi saya bahwa akan lebih baik.)
Telemakus
9
Keduanya stat()dan access()menderita kerentanan TOCTOU (demikian juga lstat(), tetapi fstat()aman). Itu tergantung apa yang akan Anda lakukan berdasarkan ada atau tidaknya file. Menggunakan opsi yang tepat untuk open()biasanya merupakan cara terbaik untuk mengatasi masalah, tetapi bisa rumit merumuskan pilihan yang tepat. Lihat juga diskusi tentang EAFP (Lebih Mudah Meminta Pengampunan daripada Izin) dan LBYL (Look Before You Leap) - lihat LBYL vs EAFP di Jawa , misalnya.
Jonathan Leffler
87

Biasanya ketika Anda ingin memeriksa apakah ada file, itu karena Anda ingin membuat file itu jika tidak. Jawaban Graeme Perrow baik jika Anda tidak ingin membuat file itu, tetapi rentan terhadap kondisi balapan jika Anda melakukannya: proses lain dapat membuat file di antara Anda memeriksa apakah ada, dan Anda benar-benar membukanya untuk menulisnya . (Jangan tertawa ... ini bisa berimplikasi keamanan yang buruk jika file yang dibuat adalah symlink!)

Jika Anda ingin memeriksa keberadaan dan membuat file jika tidak ada, secara atomis sehingga tidak ada kondisi ras, maka gunakan ini:

#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
sumber
8
Jika Anda akan menggunakan O_CREAT, Anda harus menyediakan mode (izin) sebagai argumen ketiga untuk dibuka (). Juga pertimbangkan apakah O_TRUNC atau O_EXCL atau O_APPEND harus digunakan.
Jonathan Leffler
6
Jonathan Leffler benar, contoh ini membutuhkan O_EXCL untuk bekerja seperti yang tertulis.
Randy Proctor
6
Selain itu, Anda perlu menentukan mode sebagai argumen ketiga: terbuka (mengunci, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR)
andrew cooke
4
Perlu dicatat bahwa ini hanya seaman sistem file yang kompatibel dengan POSIX; khususnya, versi lama NFS memiliki kondisi ras yang seharusnya dihindari oleh O_EXCL! Ada solusi, didokumentasikan dalam open(2)(di Linux; halaman manual OS Anda mungkin bervariasi), tetapi itu agak jelek dan mungkin tidak tahan terhadap penyerang jahat.
Kevin
Perhatikan bahwa untuk menggunakan ini FILE*, Anda harus menggunakan metode posix fdopen(fd,"flags")untuk menghasilkanFILE*
Gem Taylor
32

Iya. Gunakan stat(). Lihat halaman manual untuk stat(2).

stat()akan gagal jika file tidak ada, jika tidak kemungkinan besar berhasil. Jika memang ada, tetapi Anda tidak memiliki akses baca ke direktori di mana ia ada, itu juga akan gagal, tetapi dalam hal itu metode apa pun akan gagal (bagaimana Anda bisa memeriksa konten direktori yang mungkin tidak Anda lihat sesuai dengan hak akses? Cukup, Anda tidak bisa).

Oh, seperti orang lain sebutkan, Anda juga bisa menggunakannya access(). Namun saya lebih suka stat(), seolah-olah file itu ada akan segera memberi saya banyak informasi berguna (kapan terakhir diperbarui, seberapa besar itu, pemilik dan / atau grup yang memiliki file, izin akses, dan sebagainya).

Mecki
sumber
5
akses lebih disukai jika Anda hanya perlu tahu apakah file itu ada. Stat () dapat didengar orang banyak jika Anda tidak membutuhkan semua info tambahan.
Martin Beckett
4
Sebenarnya ketika saya mendaftar direktori menggunakan ls-command, ia memanggil stat untuk setiap file yang ada di sana dan menjalankan ls memiliki overhead yang besar cukup baru bagi saya. Sebenarnya Anda dapat menjalankan ls pada direktori dengan ribuan file dan kembali dalam sepersekian detik.
Mecki
2
@Mecki: stat memiliki overhead tambahan yang tidak nol dibandingkan dengan akses pada sistem yang mendukung hardlink. Ini karena akses hanya perlu melihat entri direktori, sedangkan stat juga harus mencari inode. Pada perangkat penyimpanan dengan waktu pencarian yang buruk (mis. Kaset), perbedaannya dapat menjadi signifikan karena entri direktori dan inode tidak mungkin bersebelahan.
Kevin
3
@Kevin Kecuali jika Anda hanya meneruskan F_OK ke sana, access()periksa izin akses file dari suatu file dan ini disimpan di dalam inode untuk file itu dan tidak dalam entri direktori (setidaknya untuk semua sistem file yang memiliki struktur seperti inode) . Jadi access()harus mengakses inode dengan cara yang sama persis dengan yang stat()mengaksesnya. Jadi apa yang Anda katakan hanya berlaku jika Anda tidak memeriksa izin! Dan sebenarnya pada beberapa sistem access()bahkan diimplementasikan di atas stat()(misalnya glibc pada GNU Hurd melakukannya seperti itu), jadi tidak ada jaminan di tempat pertama.
Mecki
@Mecki: Siapa yang mengatakan sesuatu tentang memeriksa izin? Saya secara khusus berbicara tentang F_OK. Dan ya, beberapa sistem diimplementasikan dengan buruk. Akses akan setidaknya secepat stat dalam setiap kasus dan mungkin lebih cepat beberapa waktu.
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
sumber
1
Bukankah ini menyebabkan kebocoran memori? Anda tidak pernah menutup file jika ada.
LegionMammal978
1
Ini adalah metode yang bagus dan sederhana. Jika Anda menggunakan Windows MSVC, gunakan ini sebagai gantinya:(fopen_s(file, "sample.txt", "r")) karena fopen()dianggap sudah usang (atau nonaktifkan kesalahan yang sudah usang, tetapi itu tidak disarankan).
Nikos
15
fopen()adalah standar C, tidak ke mana-mana. Ini hanya "usang" oleh Microsoft. Jangan gunakan fopen_s()kecuali Anda menginginkan kode khusus platform, non-portabel.
Andrew Henle
Memanggil fclose () tanpa alasan? Diperlukan untuk menetapkan variabel 'file' terlebih dahulu!
Jenix
1
Variabel 'file' di sini memiliki nilai sampah. Kenapa repot-repot menutupnya? Anda hanya memanggil 'fclose (SOME_RANDOM_ADDRESS);' ..
Jenix
6

Dari bantuan Visual C ++, saya cenderung untuk ikut

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

Juga perlu diperhatikan nilai mode :_access(const char *path,int mode)

  • 00: Hanya ada

  • 02: Izin menulis

  • 04: Baca izin

  • 06: Baca dan tulis izin

Karena Anda fopenbisa gagal dalam situasi di mana file itu ada tetapi tidak dapat dibuka seperti yang diminta.

Sunting: Baca saja pos Mecki. stat()memang terlihat seperti cara yang lebih rapi untuk pergi. Ho hum.

SmacL
sumber
akses lebih disukai jika Anda hanya perlu tahu apakah file itu ada. Stat () dapat didengar orang banyak.
Martin Beckett
4

Anda dapat menggunakan fungsi realpath ().

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

Saya pikir fungsi access () , yang ditemukan di unistd.hadalah pilihan yang baik untuk Linux(Anda dapat menggunakan stat juga).

Anda dapat menggunakannya seperti ini:

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

Dan Anda mendapatkan Output berikut:

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
sumber