Làm thế nào để đọc nội dung của một tệp thành một chuỗi trong C?


96

Cách đơn giản nhất (ít mắc lỗi nhất, ít dòng mã nhất, tuy nhiên bạn muốn diễn giải nó) để mở một tệp bằng C và đọc nội dung của nó thành một chuỗi (char *, char [], bất cứ điều gì)?


8
"cách đơn giản nhất" và "ít lỗi nhất" thường đối lập nhau.
Andy Lester

14
"cách đơn giản nhất" và "ít mắc lỗi nhất" thực sự đồng nghĩa trong cuốn sách của tôi. Ví dụ, câu trả lời trong C # là string s = File.ReadAllText(filename);. Làm thế nào mà có thể đơn giản hơn và dễ xảy ra lỗi hơn?
Mark Lakata

Câu trả lời:


145

Tôi có xu hướng chỉ tải toàn bộ bộ đệm như một bộ nhớ thô vào bộ nhớ và tự mình phân tích cú pháp. Bằng cách đó, tôi có thể kiểm soát tốt nhất những gì lib tiêu chuẩn thực hiện trên nhiều nền tảng.

Đây là một sơ khai tôi sử dụng cho việc này. bạn cũng có thể muốn kiểm tra mã lỗi cho fseek, ftell và fread. (bỏ qua cho rõ ràng).

char * buffer = 0;
long length;
FILE * f = fopen (filename, "rb");

if (f)
{
  fseek (f, 0, SEEK_END);
  length = ftell (f);
  fseek (f, 0, SEEK_SET);
  buffer = malloc (length);
  if (buffer)
  {
    fread (buffer, 1, length, f);
  }
  fclose (f);
}

if (buffer)
{
  // start to process your data / extract strings here...
}

3
Tôi cũng sẽ kiểm tra giá trị trả về của fread, vì nó có thể không thực sự đọc toàn bộ tệp do lỗi và những gì không.
freespace

6
như rmeador đã nói, fseek sẽ không thành công trên các tệp> 4GB.
KPexEA

6
Thật. Đối với các tệp lớn, giải pháp này rất tệ.
Nils Pipenbrinck

31
Vì đây là trang đích, tôi muốn chỉ ra rằng điều freadđó không chấm dứt chuỗi của bạn. Điều này có thể dẫn đến một số rắc rối.
ivan-k

18
Như @Manbroski đã nói, bộ đệm cần phải được kết thúc bằng '\ 0'. Vì vậy, tôi sẽ thay đổi buffer = malloc (length + 1);và bổ sung sau fclose: buffer[length] = '\0';(xác nhận bởi Valgrind)
soywod

26

Một giải pháp khác, rất tiếc là phụ thuộc vào hệ điều hành là ánh xạ bộ nhớ của tệp. Các lợi ích nói chung bao gồm hiệu suất đọc và giảm sử dụng bộ nhớ khi chế độ xem ứng dụng và bộ đệm tệp hệ điều hành thực sự có thể chia sẻ bộ nhớ vật lý.

Mã POSIX sẽ giống như sau:

int fd = open("filename", O_RDONLY);
int len = lseek(fd, 0, SEEK_END);
void *data = mmap(0, len, PROT_READ, MAP_PRIVATE, fd, 0);

Mặt khác, Windows phức tạp hơn một chút, và tiếc là tôi không có trình biên dịch trước mặt để kiểm tra, nhưng chức năng được cung cấp bởi CreateFileMapping()MapViewOfFile().


3
Đừng quên kiểm tra các giá trị trả về từ các lệnh gọi hệ thống đó!
Toby Speight

3
phải sử dụng off_t thay vì int khi gọi lseek ().
ivan.ukr

1
Lưu ý rằng nếu mục tiêu là ghi lại ổn định nội dung của tệp trong bộ nhớ tại một thời điểm nhất định thì nên tránh giải pháp này, trừ khi bạn chắc chắn rằng tệp đang được đọc vào bộ nhớ sẽ không bị các quy trình khác sửa đổi trong khoảng thời gian qua đó bản đồ sẽ được sử dụng. Xem bài đăng này để biết thêm thông tin.
user001

12

Nếu "đọc nội dung của nó thành một chuỗi" có nghĩa là tệp không chứa các ký tự có mã 0, bạn cũng có thể sử dụng hàm getdelim (), hàm này chấp nhận một khối bộ nhớ và phân bổ lại nó nếu cần hoặc chỉ cấp phát toàn bộ bộ đệm cho bạn và đọc tệp vào đó cho đến khi nó gặp phải dấu phân cách hoặc cuối tệp được chỉ định. Chỉ cần chuyển '\ 0' làm dấu phân cách để đọc toàn bộ tệp.

Chức năng này có sẵn trong Thư viện GNU C, http://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getdelim-994

Mã mẫu có thể trông đơn giản như

char* buffer = NULL;
size_t len;
ssize_t bytes_read = getdelim( &buffer, &len, '\0', fp);
if ( bytes_read != -1) {
  /* Success, now the entire file is in the buffer */

1
Tôi đã sử dụng cái này trước đây! Nó hoạt động rất độc đáo, giả sử tệp bạn đang đọc là văn bản (không chứa \ 0).
ephemient

ĐẸP! Tiết kiệm rất nhiều vấn đề khi đọc toàn bộ tệp văn bản. Bây giờ nếu có một cách cực kỳ đơn giản tương tự để đọc một luồng tệp nhị phân cho đến EOF mà không cần bất kỳ ký tự phân tách nào!
anthony

6

Nếu tệp là văn bản và bạn muốn lấy từng dòng văn bản, cách dễ nhất là sử dụng fgets ().

char buffer[100];
FILE *fp = fopen("filename", "r");                 // do not use "rb"
while (fgets(buffer, sizeof(buffer), fp)) {
... do something
}
fclose(fp);

6

Nếu bạn đang đọc các tệp đặc biệt như stdin hoặc đường ống, bạn sẽ không thể sử dụng fstat để lấy trước kích thước tệp. Ngoài ra, nếu bạn đang đọc tệp nhị phân, fgets sẽ mất thông tin kích thước chuỗi do các ký tự '\ 0' được nhúng. Cách tốt nhất để đọc tệp sau đó là sử dụng read và realloc:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main () {
    char buf[4096];
    ssize_t n;
    char *str = NULL;
    size_t len = 0;
    while (n = read(STDIN_FILENO, buf, sizeof buf)) {
        if (n < 0) {
            if (errno == EAGAIN)
                continue;
            perror("read");
            break;
        }
        str = realloc(str, len + n + 1);
        memcpy(str + len, buf, n);
        len += n;
        str[len] = '\0';
    }
    printf("%.*s\n", len, str);
    return 0;
}

1
Đây là O (n ^ 2), trong đó n là độ dài tệp của bạn. Tất cả các giải pháp có nhiều phiếu ủng hộ hơn giá trị này là O (n). Vui lòng không sử dụng giải pháp này trong thực tế hoặc sử dụng phiên bản đã sửa đổi với tốc độ tăng nhân.
Clark Gaebel

2
realloc () có thể mở rộng bộ nhớ hiện có lên kích thước mới mà không cần sao chép bộ nhớ cũ sang một bộ nhớ mới lớn hơn. chỉ khi có các lệnh gọi can thiệp đến malloc () thì nó mới cần di chuyển bộ nhớ xung quanh và tạo ra giải pháp O (n ^ 2). ở đây, không có lệnh gọi nào đến malloc () xảy ra giữa các lệnh gọi tới realloc () vì vậy giải pháp sẽ ổn.
Jake

2
Bạn có thể đọc trực tiếp vào bộ đệm "str" ​​(với độ lệch thích hợp) mà không cần sao chép từ "buf" trung gian. Tuy nhiên, kỹ thuật đó nói chung sẽ phân bổ quá mức bộ nhớ cần thiết cho nội dung tệp. Ngoài ra, hãy cẩn thận với các tệp nhị phân, printf sẽ không xử lý chúng một cách chính xác và có thể bạn không muốn in tệp nhị phân!
anthony

3

Lưu ý: Đây là một sửa đổi của câu trả lời được chấp nhận ở trên.

Đây là một cách để làm điều đó, hoàn thành với việc kiểm tra lỗi.

Tôi đã thêm một trình kiểm tra kích thước để thoát khi tệp lớn hơn 1 GiB. Tôi đã làm điều này vì chương trình đặt toàn bộ tệp thành một chuỗi có thể sử dụng quá nhiều ram và làm hỏng máy tính. Tuy nhiên, nếu bạn không quan tâm đến điều đó, bạn có thể xóa nó khỏi mã.

#include <stdio.h>
#include <stdlib.h>

#define FILE_OK 0
#define FILE_NOT_EXIST 1
#define FILE_TO_LARGE 2
#define FILE_READ_ERROR 3

char * c_read_file(const char * f_name, int * err, size_t * f_size) {
    char * buffer;
    size_t length;
    FILE * f = fopen(f_name, "rb");
    size_t read_length;

    if (f) {
        fseek(f, 0, SEEK_END);
        length = ftell(f);
        fseek(f, 0, SEEK_SET);

        // 1 GiB; best not to load a whole large file in one string
        if (length > 1073741824) {
            *err = FILE_TO_LARGE;

            return NULL;
        }

        buffer = (char *)malloc(length + 1);

        if (length) {
            read_length = fread(buffer, 1, length, f);

            if (length != read_length) {
                 *err = FILE_READ_ERROR;

                 return NULL;
            }
        }

        fclose(f);

        *err = FILE_OK;
        buffer[length] = '\0';
        *f_size = length;
    }
    else {
        *err = FILE_NOT_EXIST;

        return NULL;
    }

    return buffer;
}

Và để kiểm tra lỗi:

int err;
size_t f_size;
char * f_data;

f_data = c_read_file("test.txt", &err, &f_size);

if (err) {
    // process error
}

2

Nếu bạn đang sử dụng glib, thì bạn có thể sử dụng g_file_get_contents ;

gchar *contents;
GError *err = NULL;

g_file_get_contents ("foo.txt", &contents, NULL, &err);
g_assert ((contents == NULL && err != NULL) || (contents != NULL && err == NULL));
if (err != NULL)
  {
    // Report error to user, and free error
    g_assert (contents == NULL);
    fprintf (stderr, "Unable to read file: %s\n", err->message);
    g_error_free (err);
  }
else
  {
    // Use file contents
    g_assert (contents != NULL);
  }
}

1
// Assumes the file exists and will seg. fault otherwise.
const GLchar *load_shader_source(char *filename) {
  FILE *file = fopen(filename, "r");             // open 
  fseek(file, 0L, SEEK_END);                     // find the end
  size_t size = ftell(file);                     // get the size in bytes
  GLchar *shaderSource = calloc(1, size);        // allocate enough bytes
  rewind(file);                                  // go back to file beginning
  fread(shaderSource, size, sizeof(char), file); // read each char into ourblock
  fclose(file);                                  // close the stream
  return shaderSource;
}

Đây là một giải pháp khá thô thiển vì không có gì được kiểm tra so với null.


Điều này sẽ chỉ với các tệp dựa trên đĩa. Nó sẽ không thành công đối với các đường ống được đặt tên, đầu vào tiêu chuẩn hoặc luồng mạng.
anthony

Ha, cũng tại sao tôi đến đây! Nhưng tôi nghĩ bạn cần phải kết thúc chuỗi null hoặc trả về độ dài glShaderSourcetùy chọn.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1

Chỉ được sửa đổi từ câu trả lời được chấp nhận ở trên.

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

char *readFile(char *filename) {
    FILE *f = fopen(filename, "rt");
    assert(f);
    fseek(f, 0, SEEK_END);
    long length = ftell(f);
    fseek(f, 0, SEEK_SET);
    char *buffer = (char *) malloc(length + 1);
    buffer[length] = '\0';
    fread(buffer, 1, length, f);
    fclose(f);
    return buffer;
}

int main() {
    char *content = readFile("../hello.txt");
    printf("%s", content);
}

Đây không phải là mã C. Câu hỏi không được gắn thẻ là C ++.
Gerhardh

@Gerhardh Vì vậy, hãy trả lời nhanh cho câu hỏi cách đây 9 năm khi tôi đang chỉnh sửa! Mặc dù phần chức năng là C thuần túy, nhưng tôi xin lỗi vì câu trả lời sẽ-không-chạy-trên-c của tôi.
BaiJiFeiLong

Câu hỏi cổ xưa này đã được liệt kê ở đầu các câu hỏi hoạt động. Tôi đã không tìm kiếm nó.
Gerhardh

Bộ nhớ rò rỉ mã này, đừng quên để giải phóng bộ nhớ malloc'd của bạn :)
ericcurtin

0

Tôi sẽ thêm phiên bản của riêng tôi, dựa trên các câu trả lời ở đây, chỉ để tham khảo. Mã của tôi xem xét đến sizeof (char) và thêm một vài nhận xét vào nó.

// Open the file in read mode.
FILE *file = fopen(file_name, "r");
// Check if there was an error.
if (file == NULL) {
    fprintf(stderr, "Error: Can't open file '%s'.", file_name);
    exit(EXIT_FAILURE);
}
// Get the file length
fseek(file, 0, SEEK_END);
long length = ftell(file);
fseek(file, 0, SEEK_SET);
// Create the string for the file contents.
char *buffer = malloc(sizeof(char) * (length + 1));
buffer[length] = '\0';
// Set the contents of the string.
fread(buffer, sizeof(char), length, file);
// Close the file.
fclose(file);
// Do something with the data.
// ...
// Free the allocated string space.
free(buffer);

0

dễ dàng và gọn gàng (giả sử nội dung trong tệp nhỏ hơn 10000):

void read_whole_file(char fileName[1000], char buffer[10000])
{
    FILE * file = fopen(fileName, "r");
    if(file == NULL)
    {
        puts("File not found");
        exit(1);
    }
    char  c;
    int idx=0;
    while (fscanf(file , "%c" ,&c) == 1)
    {
        buffer[idx] = c;
        idx++;
    }
    buffer[idx] = 0;
}

Vui lòng không phân bổ tất cả bộ nhớ mà bạn nghĩ rằng bạn sẽ cần trả trước. Đây là một ví dụ hoàn hảo về thiết kế tồi. Bạn nên cấp phát bộ nhớ bất cứ khi nào có thể. Sẽ là một thiết kế tốt nếu bạn mong đợi tệp có độ dài 10.000 byte, chương trình của bạn không thể xử lý tệp có kích thước khác và bạn đang kiểm tra kích thước và vẫn gặp lỗi, nhưng đó không phải là những gì đang diễn ra ở đây. Bạn thực sự nên học cách viết mã C một cách chính xác.
Jack Giffin
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.