Trong C, làm cách nào để đọc tệp văn bản và in tất cả các chuỗi


94

Tôi có một tệp văn bản có tên test.txt

Tôi muốn viết một chương trình C có thể đọc tệp này và in nội dung ra bảng điều khiển (giả sử tệp chỉ chứa văn bản ASCII).

Tôi không biết cách lấy kích thước của biến chuỗi của mình. Như thế này:

char str[999];
FILE * file;
file = fopen( "test.txt" , "r");
if (file) {
    while (fscanf(file, "%s", str)!=EOF)
        printf("%s",str);
    fclose(file);
}

Kích thước 999không hoạt động vì chuỗi được trả về fscanfcó thể lớn hơn chuỗi đó. Làm sao tôi có thể giải quyết việc này?

Câu trả lời:


134

Cách đơn giản nhất là đọc một ký tự và in nó ngay sau khi đọc:

int c;
FILE *file;
file = fopen("test.txt", "r");
if (file) {
    while ((c = getc(file)) != EOF)
        putchar(c);
    fclose(file);
}

cintở trên, vì EOFlà một số âm, và một đồng bằng charcó thể unsigned.

Nếu bạn muốn đọc tệp theo nhiều phần, nhưng không có phân bổ bộ nhớ động, bạn có thể thực hiện:

#define CHUNK 1024 /* read 1024 bytes at a time */
char buf[CHUNK];
FILE *file;
size_t nread;

file = fopen("test.txt", "r");
if (file) {
    while ((nread = fread(buf, 1, sizeof buf, file)) > 0)
        fwrite(buf, 1, nread, stdout);
    if (ferror(file)) {
        /* deal with error */
    }
    fclose(file);
}

Phương pháp thứ hai ở trên về cơ bản là cách bạn sẽ đọc một tệp với một mảng được cấp phát động:

char *buf = malloc(chunk);

if (buf == NULL) {
    /* deal with malloc() failure */
}

/* otherwise do this.  Note 'chunk' instead of 'sizeof buf' */
while ((nread = fread(buf, 1, chunk, file)) > 0) {
    /* as above */
}

Phương thức fscanf()với %sđịnh dạng của bạn làm mất thông tin về khoảng trắng trong tệp, vì vậy nó không chính xác sao chép tệp sang stdout.


Có thể đọc dữ liệu từ tệp mà không cần mở tệp đó bằng c / c ++ không ??
Sagar Patel

Điều gì sẽ xảy ra nếu tệp văn bản chứa các giá trị số nguyên được phân tách bằng dấu phẩy? hơn mã sẽ là gì, bạn cũng có thể chỉnh sửa câu trả lời của mình bằng mã đó.
Mohsin

Ở trên phù hợp với bất kỳ loại tệp văn bản nào. Nếu bạn muốn phân tích cú pháp các số từ tệp CSV, đó là một vấn đề khác.
Alok Singhal

1
@overexchange Câu hỏi không nói về dòng - nó là về việc đọc một tệp và sao chép nội dung của nó vào stdout.
Alok Singhal

1
@shjeff Một tệp không được chứa ký tự EOF. Lưu ý rằng đó clà int và C sẽ đảm bảo rằng EOFkhông bằng bất kỳ ký tự hợp lệ nào.
Alok Singhal

60

Có rất nhiều câu trả lời hay ở đây về việc đọc nó theo từng đoạn, tôi sẽ chỉ cho bạn một mẹo nhỏ là đọc tất cả nội dung cùng một lúc vào bộ đệm và in nó.

Tôi không nói nó tốt hơn. Nó không phải, và như Ricardo đôi khi nó có thể tồi tệ, nhưng tôi thấy đó là một giải pháp tốt cho những trường hợp đơn giản.

Tôi rắc nó với những bình luận vì có rất nhiều điều đang diễn ra.

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

char* ReadFile(char *filename)
{
   char *buffer = NULL;
   int string_size, read_size;
   FILE *handler = fopen(filename, "r");

   if (handler)
   {
       // Seek the last byte of the file
       fseek(handler, 0, SEEK_END);
       // Offset from the first to the last byte, or in other words, filesize
       string_size = ftell(handler);
       // go back to the start of the file
       rewind(handler);

       // Allocate a string that can hold it all
       buffer = (char*) malloc(sizeof(char) * (string_size + 1) );

       // Read it all in one operation
       read_size = fread(buffer, sizeof(char), string_size, handler);

       // fread doesn't set it so put a \0 in the last position
       // and buffer is now officially a string
       buffer[string_size] = '\0';

       if (string_size != read_size)
       {
           // Something went wrong, throw away the memory and set
           // the buffer to NULL
           free(buffer);
           buffer = NULL;
       }

       // Always remember to close the file.
       fclose(handler);
    }

    return buffer;
}

int main()
{
    char *string = ReadFile("yourfile.txt");
    if (string)
    {
        puts(string);
        free(string);
    }

    return 0;
}

Hãy cho tôi biết nếu nó hữu ích hoặc bạn có thể học được điều gì đó từ nó :)


2
Nó không nên đọc buffer[string_size] = '\0';thay vì string_size+1? Afaik chuỗi thực sự đi từ 0đến string_size-1\0ký tự do đó cần phải ở string_size, phải không?
aepsil0n

4
Sử dụng ftellfseekđể tìm ra kích thước của một tập tin là không an toàn: securecoding.cert.org/confluence/display/seccode/...
Joakim

1
Mã này chứa một lỗ hổng bộ nhớ, bạn không bao giờ đóng tệp. Có một sự mất tíchfclose(handle)
Joakim

1
Có một lỗi đánh máy mà bạn gọi fclose (xử lý), nó phải là fclose (handler)
Eduardo Cobuci

3
Bạn có thể sử dụng calloc(2)thay vì malloc(1)bỏ qua việc phải đặt dấu chấm dứt rỗng.

14

Thay vào đó, chỉ cần in trực tiếp các ký tự lên bảng điều khiển vì tệp văn bản có thể rất lớn và bạn có thể yêu cầu nhiều bộ nhớ.

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

int main() {

    FILE *f;
    char c;
    f=fopen("test.txt","rt");

    while((c=fgetc(f))!=EOF){
        printf("%c",c);
    }

    fclose(f);
    return 0;
}

6

Sử dụng "read ()" thay vì o fscanf:

ssize_t read(int fildes, void *buf, size_t nbyte);

SỰ MIÊU TẢ

Hàm read () sẽ cố gắng đọc các nbytebyte từ tệp được liên kết với bộ mô tả tệp đang mở fildes, vào bộ đệm được trỏ tới buf.

Đây là một ví dụ:

http://cmagical.blogspot.com/2010/01/c-programming-on-unix-implecting-cat.html

Phần làm việc từ ví dụ đó:

f=open(argv[1],O_RDONLY);
while ((n=read(f,l,80)) > 0)
    write(1,l,n);

Một cách tiếp cận thay thế là sử dụng getc/ putcđể đọc / ghi 1 ký tự tại một thời điểm. Ít hiệu quả hơn rất nhiều. Một ví dụ điển hình: http://www.eskimo.com/~scs/cclass/notes/sx13.html


readsẽ cho phép bạn đọc với một số ký tự nhất định. Đọc đủ để lấp đầy bộ đệm của bạn, sau đó đổ bộ đệm của bạn ra màn hình, xóa nó ra và lặp lại cho đến khi bạn đến cuối tệp.
bta

1

Có hai cách tiếp cận đáng chú ý.

Đầu tiên, không sử dụng scanf. Sử dụng fgets()tham số để chỉ định kích thước bộ đệm và giữ nguyên mọi ký tự dòng mới. Một vòng lặp đơn giản trên tệp in nội dung bộ đệm sẽ sao chép nguyên vẹn tệp một cách tự nhiên.

Thứ hai, sử dụng fread()hoặc thành ngữ C thông dụng với fgetc(). Những điều này sẽ xử lý tệp theo các khối có kích thước cố định hoặc một ký tự tại một thời điểm.

Nếu bạn phải xử lý tệp qua các chuỗi được phân cách bằng khoảng trắng, thì hãy sử dụng fgetshoặc freadđể đọc tệp, và một cái gì đó giống như strtokchia bộ đệm tại khoảng trắng. Đừng quên xử lý quá trình chuyển đổi từ bộ đệm này sang bộ đệm tiếp theo, vì các chuỗi mục tiêu của bạn có khả năng kéo dài ranh giới bộ đệm.

Nếu có yêu cầu bên ngoài để sử dụng scanfđể thực hiện việc đọc, thì hãy giới hạn độ dài của chuỗi mà nó có thể đọc bằng một trường chính xác trong trình định dạng. Trong trường hợp của bạn với bộ đệm 999 byte, hãy nói scanf("%998s", str);cái nào sẽ ghi nhiều nhất 998 ký tự vào bộ đệm để lại chỗ cho dấu chấm dứt nul. Nếu các chuỗi đơn dài hơn bộ đệm của bạn được phép, thì bạn sẽ phải xử lý chúng thành hai phần. Nếu không, bạn có cơ hội nói với người dùng về lỗi một cách lịch sự mà không tạo lỗ hổng bảo mật tràn bộ đệm.

Bất kể, hãy luôn xác thực các giá trị trả về và suy nghĩ về cách xử lý đầu vào xấu, độc hại hoặc chỉ không đúng định dạng.


1

Bạn có thể sử dụng fgetsvà giới hạn kích thước của chuỗi đọc.

char *fgets(char *str, int num, FILE *stream);

Bạn có thể thay đổi whilemã của mình thành:

while (fgets(str, 100, file)) /* printf("%s", str) */;

0

Bạn có thể đọc toàn bộ tệp với phân bổ bộ nhớ động, nhưng không phải là một ý kiến ​​hay vì nếu tệp quá lớn, bạn có thể gặp sự cố về bộ nhớ.

Vì vậy, tốt hơn là đọc các phần ngắn của tệp và in nó.

#include <stdio.h>
#define BLOCK   1000

int main() {
    FILE *f=fopen("teste.txt","r");
    int size;
    char buffer[BLOCK];
    // ...
    while((size=fread(buffer,BLOCK,sizeof(char),f)>0)
            fwrite(buffer,size,sizeof(char),stdout);
    fclose(f);
    // ...
    return 0;
}
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.