Cách tốt nhất để kiểm tra nếu một tập tin tồn tại trong C?


436

Có cách nào tốt hơn là cố gắng mở tập tin không?

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

Tôi nghĩ rằng tôi sẽ đưa ra câu trả lời cho phương thức truy cập, mặc dù phương thức stat là một phương án rất hợp lý, truy cập sẽ hoàn thành công việc.
Dave Marshall

1
Bạn có thực sự chỉ muốn kiểm tra sự tồn tại? Hoặc bạn có muốn kiểm tra và ghi vào tệp nếu nó không tồn tại. Nếu vậy, hãy xem câu trả lời của tôi dưới đây, để biết phiên bản không bị các điều kiện chủng tộc.
Dan Lenski

6
tôi không thấy - có gì sai với cách fopen / fclose đó?
Johannes Schaub - litb

16
@ JohannesSchaub-litb: một điều không đúng với phương thức fopen()/ fclose()là bạn có thể không mở được tệp để đọc ngay cả khi nó tồn tại. Ví dụ, /dev/kmemtồn tại, nhưng hầu hết các quy trình không thể mở nó ngay cả để đọc. /etc/shadowlà một tập tin như vậy. Tất nhiên, cả hai stat()access()dựa vào việc có thể truy cập vào thư mục chứa tệp; tất cả các cược sẽ tắt nếu bạn không thể làm điều đó (không có quyền thực thi trên thư mục chứa tệp).
Jonathan Leffler

1
if (file = fopen(fname, "r"))sẽ đưa ra một cảnh báo. Sử dụng dấu ngoặc đơn xung quanh câu lệnh bên trong câu lệnh ifif ((file = fopen(fname, "r")))
Joakim

Câu trả lời:


595

Tra cứu các access()chức năng, tìm thấy trong unistd.h. Bạn có thể thay thế chức năng của bạn với

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

Bạn cũng có thể sử dụng R_OK, W_OKX_OKở vị trí của F_OKkiểm tra cho phép đọc, quyền ghi và quyền thực thi (tương ứng) chứ không phải là sự tồn tại, và bạn có thể HOẶC ai trong số họ với nhau (tức là kiểm tra cho cả đọc quyền ghi sử dụng R_OK|W_OK)

Cập nhật : Lưu ý rằng trên Windows, bạn không thể sử dụng W_OKđể kiểm tra quyền ghi một cách đáng tin cậy, vì chức năng truy cập không đưa các tài khoản DACL vào tài khoản. access( fname, W_OK )có thể trả về 0 (thành công) vì tệp không có tập thuộc tính chỉ đọc, nhưng bạn vẫn có thể không có quyền ghi vào tệp.


67
POSIX là một tiêu chuẩn ISO; nó định nghĩa truy cập (). C là một tiêu chuẩn ISO khác; nó không.
Jonathan Leffler

16
Có những cạm bẫy liên quan đến access (). Có một cửa sổ TOCTOU (thời gian kiểm tra, thời gian sử dụng) lỗ hổng giữa việc sử dụng access () và bất cứ điều gì khác mà bạn làm sau đó. [... sẽ được tiếp tục ...]
Jonathan Leffler

23
[... Tiếp tục ...] Thay vì bí mật hơn, trên các hệ thống POSIX, access () kiểm tra xem UID thực và GID thực, thay vì UID hiệu quả và GID hiệu quả. Điều này chỉ quan trọng đối với các chương trình setuid hoặc setgid, nhưng sau đó nó rất quan trọng vì nó có thể đưa ra câu trả lời 'sai'.
Jonathan Leffler

3
Tôi đã chạy qua câu hỏi này khi tìm kiếm lý do access()đã phá vỡ mã của tôi. Tôi đã chuyển từ DevC ++ sang CodeBlocks và nó đã ngừng hoạt động. Vì vậy, nó không phải là không thể sai lầm; Thêm +1 cho @Leffler.
Ben

11
Hầu hết thời gian, có (sử dụng access()để kiểm tra sự tồn tại của tệp), nhưng trong chương trình SUID hoặc SGID, thậm chí điều đó có thể không chính xác. Nếu tệp được kiểm tra nằm trong một thư mục mà UID thực hoặc GID thực không thể truy cập, access()có thể báo cáo không có tệp đó khi nó tồn tại. Bí truyền và không thể? Đúng.
Jonathan Leffler

116

Sử dụng statnhư thế này:

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

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

và gọi nó như thế này:

#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: trên các hệ thống như vậy, rất có thể access()cũng có vấn đề và có các tùy chọn để sử dụng để tạo access()stat()làm việc với các tệp lớn (lớn hơn 2 GB).
Jonathan Leffler

14
Bạn có thể chỉ ra tài liệu liên quan đến lỗi sau 2 GB không? Ngoài ra, sự thay thế trong những trường hợp như vậy là gì?
chamakits

@JonathanLeffler Liệu statkhông bị lỗ hổng TOCTOU giống như access? (Tôi không rõ ràng rằng nó sẽ tốt hơn.)
Telemachus

9
Cả hai stat()access()bị lỗ hổng TOCTOU (cũng vậy lstat(), nhưng fstat()an toàn). Nó phụ thuộc vào những gì bạn sẽ làm dựa trên sự hiện diện hay vắng mặt của tập tin. Sử dụng các tùy chọn chính xác open()thường là cách tốt nhất để xử lý các vấn đề, nhưng nó có thể khó khăn trong việc hình thành các tùy chọn đúng. Xem thêm các cuộc thảo luận về EAFP (Dễ xin tha thứ hơn so với quyền) và LBYL (Nhìn trước khi bạn nhảy) - ví dụ, xem LBYL vs EAFP trong Java .
Jonathan Leffler

87

Thông thường khi bạn muốn kiểm tra xem một tệp có tồn tại hay không, đó là vì bạn muốn tạo tệp đó nếu không. Câu trả lời của Graeme Perrow là tốt nếu bạn không muốn tạo tệp đó, nhưng nó dễ bị điều kiện chủng tộc nếu bạn thực hiện: một quy trình khác có thể tạo tệp ở giữa bạn kiểm tra xem nó có tồn tại không và bạn thực sự mở nó để ghi vào nó . (Đừng cười ... điều này có thể có ý nghĩa bảo mật xấu nếu tệp được tạo là một liên kết tượng trưng!)

Nếu bạn muốn kiểm tra cho sự tồn tại tạo ra các tập tin nếu nó không tồn tại, nguyên tử để không có điều kiện chủng tộc, sau đó sử dụng này:

#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
Nếu bạn định sử dụng O_CREAT, bạn cần cung cấp chế độ (quyền) làm đối số thứ ba để mở (). Đồng thời xem xét liệu O_TRUNC hoặc O_EXCL hoặc O_APPEND nên được sử dụng.
Jonathan Leffler

6
Jonathan Leffler đã đúng, ví dụ này yêu cầu O_EXCL hoạt động như văn bản.
Randy Proctor

6
Ngoài ra, bạn cần chỉ định chế độ làm đối số thứ ba: open (lock, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR)
rút cooke

4
Cần lưu ý rằng điều này chỉ an toàn khi hệ thống tập tin tuân thủ POSIX; đặc biệt, các phiên bản cũ của NFS có điều kiện đua O_EXCL rất cần phải tránh! Có một cách giải quyết, được ghi lại trong open(2)(trên Linux; các trang người dùng trong hệ điều hành của bạn có thể khác nhau), nhưng nó khá xấu và có thể không chống lại được kẻ tấn công độc hại.
Kevin

Lưu ý rằng để sử dụng điều này với FILE*, sau đó bạn cần sử dụng phương thức posix fdopen(fd,"flags")để tạo mộtFILE*
Gem Taylor

32

Đúng. Sử dụng stat(). Xem trang người đàn ông cho stat(2).

stat()sẽ thất bại nếu tập tin không tồn tại, nếu không rất có thể thành công. Nếu nó tồn tại, nhưng bạn không có quyền truy cập đọc vào thư mục nơi nó tồn tại, nó cũng sẽ thất bại, nhưng trong trường hợp đó, bất kỳ phương pháp nào cũng sẽ thất bại (làm thế nào bạn có thể kiểm tra nội dung của thư mục mà bạn có thể không thấy theo quyền truy cập? Đơn giản, bạn không thể).

Ồ, như một người khác đã đề cập, bạn cũng có thể sử dụng access(). Tuy nhiên, tôi thích stat(), vì nếu tệp tồn tại, nó sẽ ngay lập tức cung cấp cho tôi nhiều thông tin hữu ích (khi được cập nhật lần cuối, chủ sở hữu và / hoặc nhóm sở hữu tệp, quyền truy cập, v.v.) lớn đến mức nào.


5
truy cập được ưu tiên nếu bạn chỉ cần biết nếu tập tin tồn tại. Stat () có thể có một tình cờ nghe lớn nếu bạn không cần tất cả các thông tin bổ sung.
Martin Beckett

4
Trên thực tế khi tôi liệt kê một thư mục sử dụng lệnh ls, nó gọi stat cho mọi tệp có mặt ở đó và việc chạy ls có một chi phí lớn là khá mới đối với tôi. Trên thực tế, bạn có thể chạy ls trên các thư mục với hàng ngàn tệp và nó sẽ trả về trong một phần của giây.
Mecki

2
@Mecki: stat có chi phí bổ sung khác so với truy cập trên các hệ thống hỗ trợ liên kết cứng. Điều này là do truy cập chỉ phải xem mục nhập thư mục, trong khi stat cũng phải tìm inode. Trên các thiết bị lưu trữ có thời gian tìm kiếm không tốt (ví dụ: băng từ), sự khác biệt có thể là đáng kể do mục nhập thư mục và inode không có khả năng nằm cạnh nhau.
Kevin

3
@Kevin Trừ khi bạn chỉ chuyển F_OK cho nó, access()kiểm tra quyền truy cập tệp của tệp và chúng được lưu trong inode cho tệp đó và không nằm trong mục nhập thư mục của nó (ít nhất là đối với tất cả các hệ thống tệp có cấu trúc giống như inode) . Vì vậy, access()phải truy cập vào inode chính xác giống như cách stat()truy cập nó. Vì vậy, những gì bạn nói chỉ đúng nếu bạn không kiểm tra bất kỳ quyền nào! Và thực tế trên một số hệ thống access()thậm chí còn được triển khai trên đầu trang stat()(ví dụ như glibc trên GNU Hurd thực hiện theo cách đó), vì vậy không có gì đảm bảo ở nơi đầu tiên.
Mecki

@Mecki: Ai nói về việc kiểm tra quyền? Tôi đặc biệt nói về F_OK. Và vâng, một số hệ thống được thực hiện kém. Truy cập sẽ ít nhất là nhanh như stat trong mọi trường hợp và có thể nhanh hơn một số thời gian.
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
Điều này sẽ không gây rò rỉ bộ nhớ? Bạn không bao giờ đóng tệp nếu nó tồn tại.
LegionMammal978

1
Đây là một phương pháp tốt, đơn giản. Nếu bạn đang ở trong Windows MSVC, hãy sử dụng điều này thay vào đó: (fopen_s(file, "sample.txt", "r"))fopen()được coi là không dùng nữa (hoặc vô hiệu hóa các lỗi không dùng nữa, nhưng điều đó không được khuyến nghị).
Nikos

15
fopen()là tiêu chuẩn C, nó sẽ không đi đâu cả. Nó chỉ bị "phản đối" bởi Microsoft. Không sử dụng fopen_s()trừ khi bạn muốn mã không di động, dành riêng cho nền tảng.
Andrew Henle

Gọi fclose () để làm gì? Cần thiết để gán biến 'tập tin' đầu tiên!
Jenix

1
Biến 'tệp' ở đây có giá trị rác. Tại sao phải đóng nó ở nơi đầu tiên? Bạn chỉ đang gọi 'fc Đóng (SOME_RANDOM_ADDRESS);' ..
Jenix

6

Từ trợ giúp của Visual C ++, tôi có xu hướng đi cùng

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

Cũng đáng lưu ý các giá trị chế độ của :_access(const char *path,int mode)

  • 00: Chỉ tồn tại

  • 02: Viết giấy phép

  • 04: Đọc quyền

  • 06: Đọc và viết cho phép

Vì bạn fopencó thể thất bại trong các tình huống tồn tại tệp nhưng không thể mở theo yêu cầu.

Chỉnh sửa: Chỉ cần đọc bài viết của Mecki. stat()trông giống như một cách gọn gàng hơn để đi. Hồ hum.


truy cập được ưu tiên nếu bạn chỉ cần biết nếu tập tin tồn tại. Stat () có thể có một tình cờ nghe lớn.
Martin Beckett

4

Bạn có thể sử dụng hàm realpath ().

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

3

Tôi nghĩ rằng hàm access () , được tìm thấy trong unistd.hlà một lựa chọn tốt cho Linux(bạn cũng có thể sử dụng stat ).

Bạn có thể sử dụng nó như thế này:

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

Và bạn nhận được kết quả sau:

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.