C đọc từng dòng tệp


184

Tôi đã viết chức năng này để đọc một dòng từ một tập tin:

const char *readLine(FILE *file) {

    if (file == NULL) {
        printf("Error: file pointer is null.");
        exit(1);
    }

    int maximumLineLength = 128;
    char *lineBuffer = (char *)malloc(sizeof(char) * maximumLineLength);

    if (lineBuffer == NULL) {
        printf("Error allocating memory for line buffer.");
        exit(1);
    }

    char ch = getc(file);
    int count = 0;

    while ((ch != '\n') && (ch != EOF)) {
        if (count == maximumLineLength) {
            maximumLineLength += 128;
            lineBuffer = realloc(lineBuffer, maximumLineLength);
            if (lineBuffer == NULL) {
                printf("Error reallocating space for line buffer.");
                exit(1);
            }
        }
        lineBuffer[count] = ch;
        count++;

        ch = getc(file);
    }

    lineBuffer[count] = '\0';
    char line[count + 1];
    strncpy(line, lineBuffer, (count + 1));
    free(lineBuffer);
    const char *constLine = line;
    return constLine;
}

Hàm đọc tệp chính xác và sử dụng printf Tôi thấy rằng chuỗi constLine cũng đã được đọc chính xác.

Tuy nhiên, nếu tôi sử dụng chức năng, ví dụ như thế này:

while (!feof(myFile)) {
    const char *line = readLine(myFile);
    printf("%s\n", line);
}

printf đầu ra vô nghĩa. Tại sao?


Sử dụng fgetsthay vì fgetc. Bạn đang đọc từng ký tự thay vì từng dòng.
Shiv

3
Lưu ý rằng đó getline()là một phần của POSIX 2008. Có thể có các nền tảng giống POSIX mà không có nó, đặc biệt là nếu chúng không hỗ trợ phần còn lại của POSIX 2008, nhưng trong thế giới của các hệ thống POSIX, getline()ngày nay rất dễ mang theo.
Jonathan Leffler

Câu trả lời:


304

Nếu nhiệm vụ của bạn không phải là phát minh ra chức năng đọc từng dòng, mà chỉ để đọc từng dòng tệp, bạn có thể sử dụng một đoạn mã điển hình liên quan đến getline()chức năng (xem trang hướng dẫn tại đây ):

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

int main(void)
{
    FILE * fp;
    char * line = NULL;
    size_t len = 0;
    ssize_t read;

    fp = fopen("/etc/motd", "r");
    if (fp == NULL)
        exit(EXIT_FAILURE);

    while ((read = getline(&line, &len, fp)) != -1) {
        printf("Retrieved line of length %zu:\n", read);
        printf("%s", line);
    }

    fclose(fp);
    if (line)
        free(line);
    exit(EXIT_SUCCESS);
}

83
Đó không phải là di động.
JeremyP

16
Chính xác hơn, điều này getlinelà dành riêng cho GNU libc, tức là với Linux. Tuy nhiên, nếu mục đích là có chức năng đọc dòng (trái ngược với việc học C), có một số chức năng đọc dòng tên miền công khai có sẵn trên web.
Gilles 'SO- ngừng trở nên xấu xa'

11
Tại sao tôi nên làm điều đó? Đọc hướng dẫn, bộ đệm được phân bổ lại ở mỗi cuộc gọi, sau đó nó sẽ được giải phóng vào cuối.
mbaitoff

29
Việc if(line)kiểm tra là thừa. Gọi điện thoại free(NULL)về cơ bản là không có.
aroth

50
Đối với những người nói rằng getline này là dành riêng cho GNU libc, "Cả getline () và getd006 () ban đầu đều là các phần mở rộng GNU. Chúng được chuẩn hóa trong POSIX.1-2008."
willkill07

37
FILE* filePointer;
int bufferLength = 255;
char buffer[bufferLength];

filePointer = fopen("file.txt", "r");

while(fgets(buffer, bufferLength, filePointer)) {
    printf("%s\n", buffer);
}

fclose(filePointer);

Đối với tôi điều này dẫn đến việc ghi đè từng dòng với dòng tiếp theo. Xem câu hỏi này dựa trên câu trả lời trên.
Cezar Cobuz

5
Tại sao diễn viên (FILE*) fp? Không phải fplà đã FILE *và cũng fopen()trả về a FILE *?
Kế toán م

1
Nếu bạn ổn với các dòng bị giới hạn ở một độ dài nhất định, đây là câu trả lời tốt nhất. Nếu không sử dụng getlinelà một thay thế tốt. Tôi đồng ý các FILE *diễn viên là không cần thiết.
ngọn lửa

Tôi đã loại bỏ các diễn viên không cần thiết, thêm một biến cho chiều dài bộ đệm và thay đổi fpđể filePointerrõ ràng hơn.
Cướp

21

Trong readLinehàm của bạn , bạn trả về một con trỏ tới linemảng (Nói đúng ra, một con trỏ tới ký tự đầu tiên của nó, nhưng sự khác biệt là không liên quan ở đây). Vì là biến tự động (nghĩa là nó trên stack stack), bộ nhớ được lấy lại khi hàm trả về. Bạn thấy vô nghĩa vì printfđã đặt công cụ của riêng mình lên ngăn xếp.

Bạn cần trả về một bộ đệm được phân bổ động từ hàm. Bạn đã có một, nó lineBuffer; tất cả những gì bạn phải làm là cắt ngắn nó theo chiều dài mong muốn.

    lineBuffer[count] = '\0';
    realloc(lineBuffer, count + 1);
    return lineBuffer;
}

ADDED (trả lời câu hỏi tiếp theo trong bình luận): readLinetrả về một con trỏ tới các ký tự tạo thành dòng. Con trỏ này là những gì bạn cần để làm việc với nội dung của dòng. Đó cũng là những gì bạn phải chuyển đến freekhi bạn sử dụng xong bộ nhớ được lấy bởi các nhân vật này. Đây là cách bạn có thể sử dụng readLinechức năng:

char *line = readLine(file);
printf("LOG: read a line: %s\n", line);
if (strchr(line, 'a')) { puts("The line contains an a"); }
/* etc. */
free(line);
/* After this point, the memory allocated for the line has been reclaimed.
   You can't use the value of `line` again (though you can assign a new value
   to the `line` variable if you want). */

@Iron: Tôi đã thêm một cái gì đó vào câu trả lời của mình, nhưng tôi không chắc khó khăn của bạn là gì nên nó có thể không đúng.
Gilles 'SO- ngừng trở nên xấu xa'

@Iron: câu trả lời là bạn không giải phóng nó. Bạn tài liệu (trong tài liệu API) thực tế rằng bộ đệm được trả về là malloc'd ansd cần được giải phóng bởi người gọi. Sau đó, những người sử dụng hàm readLine của bạn sẽ (hy vọng!) Viết mã tương tự như đoạn trích mà Gilles đã thêm vào câu trả lời của anh ta.
JeremyP

15
//open and get the file handle
FILE* fh;
fopen_s(&fh, filename, "r");

//check if file exists
if (fh == NULL){
    printf("file does not exists %s", filename);
    return 0;
}


//read line by line
const size_t line_size = 300;
char* line = malloc(line_size);
while (fgets(line, line_size, fh) != NULL)  {
    printf(line);
}
free(line);    // dont forget to free heap memory

1
Có một số vấn đề với mã này: fopen_slàm cho mã không thể truy cập được. printfsẽ tìm kiếm định dạng specifiers và không in dấu phần trăm và nhân vật sau đây (s) như họ đang có . Null byte sẽ làm cho tất cả các ký tự trong phần còn lại của dòng biến mất. (Đừng nói với tôi rằng byte null có thể xảy ra!)
hagello 17/03/2016

Và nhân tiện, bạn không giải quyết vấn đề. OP mô tả rằng giá trị trả về của hàm biến mất. Tôi không thấy bạn giải quyết vấn đề này.
hagello 17/03/2016

@Hartley Tôi biết đây là một bình luận cũ hơn, nhưng tôi đang thêm nó để ai đó không đọc bình luận của anh ấy và cố gắng giải phóng (dòng) trong vòng lặp. Bộ nhớ cho dòng chỉ được cấp phát một lần trước khi vòng lặp bắt đầu, vì vậy nó chỉ được giải phóng một lần sau khi vòng lặp kết thúc. Nếu bạn thử giải phóng dòng bên trong vòng lặp, bạn sẽ nhận được kết quả bất ngờ. Tùy thuộc vào cách free () xử lý con trỏ. Nếu nó chỉ giải phóng bộ nhớ và để con trỏ trỏ vào vị trí cũ, mã có thể hoạt động. Nếu nó gán một giá trị khác cho con trỏ thì bạn sẽ ghi đè lên một phần khác của bộ nhớ.
alaniane

2
printf (dòng) là sai! Đừng làm điều này. Điều này mở mã của bạn đến một lỗ hổng định dạng chuỗi nơi bạn có thể tự do đọc / ghi trực tiếp vào bộ nhớ thông qua nội dung được in. Nếu tôi đặt% n /% p vào tệp và trỏ con trỏ trở lại một địa chỉ trong bộ nhớ (trong chuỗi từ tệp) mà tôi đã kiểm soát, tôi có thể thực thi mã đó.
oxagast

10

readLine() trả về con trỏ tới biến cục bộ, gây ra hành vi không xác định.

Để đi xung quanh bạn có thể:

  1. Tạo biến trong hàm người gọi và chuyển địa chỉ của nó tới readLine()
  2. Phân bổ bộ nhớ để linesử dụng malloc()- trong trường hợp linenày sẽ liên tục
  3. Sử dụng biến toàn cục, mặc dù nó thường là một thực tiễn xấu


4

Một số điều sai với ví dụ:

  • bạn đã quên thêm \ n vào bản in của mình. Ngoài ra thông báo lỗi nên đi đến stderr tức làfprintf(stderr, ....
  • (không phải là một vấn đề lớn nhưng) xem xét sử dụng fgetc()hơn là getc(). getc()là một macro, fgetc()là một chức năng thích hợp
  • getc()trả về intnhư vậy chnên được khai báo là một int. Điều này rất quan trọng vì việc so sánh với EOFsẽ được xử lý chính xác. Một số bộ ký tự 8 bit sử dụng 0xFFlàm ký tự hợp lệ (ISO-LATIN-1 sẽ là một ví dụ) và EOFlà -1, sẽ được 0xFFgán cho a char.
  • Có một lỗi tràn bộ đệm tiềm năng tại dòng

    lineBuffer[count] = '\0';

    Nếu dòng dài chính xác 128 ký tự, countlà 128 tại điểm được thực thi.

  • Như những người khác đã chỉ ra, linelà một mảng khai báo cục bộ. Bạn không thể trả lại một con trỏ cho nó.

  • strncpy(count + 1)sẽ sao chép tối đa các count + 1ký tự nhưng sẽ chấm dứt nếu nó xuất hiện '\0' Bởi vì bạn đặt lineBuffer[count]thành '\0'bạn biết nó sẽ không bao giờ nhận được count + 1. Tuy nhiên, nếu có, nó sẽ không chấm dứt '\0', vì vậy bạn cần phải làm điều đó. Bạn thường thấy một cái gì đó như sau:

    char buffer [BUFFER_SIZE];
    strncpy(buffer, sourceString, BUFFER_SIZE - 1);
    buffer[BUFFER_SIZE - 1] = '\0';
  • nếu bạn malloc()trả về một dòng (thay cho charmảng cục bộ của bạn ), kiểu trả về của bạn sẽ là char*- thả dòng const.


2
void readLine(FILE* file, char* line, int limit)
{
    int i;
    int read;

    read = fread(line, sizeof(char), limit, file);
    line[read] = '\0';

    for(i = 0; i <= read;i++)
    {
        if('\0' == line[i] || '\n' == line[i] || '\r' == line[i])
        {
            line[i] = '\0';
            break;
        }
    }

    if(i != read)
    {
        fseek(file, i - read + 1, SEEK_CUR);
    }
}

cái này thì sao?


2

Đây là vài giờ của tôi ... Đọc toàn bộ từng dòng tệp.

char * readline(FILE *fp, char *buffer)
{
    int ch;
    int i = 0;
    size_t buff_len = 0;

    buffer = malloc(buff_len + 1);
    if (!buffer) return NULL;  // Out of memory

    while ((ch = fgetc(fp)) != '\n' && ch != EOF)
    {
        buff_len++;
        void *tmp = realloc(buffer, buff_len + 1);
        if (tmp == NULL)
        {
            free(buffer);
            return NULL; // Out of memory
        }
        buffer = tmp;

        buffer[i] = (char) ch;
        i++;
    }
    buffer[i] = '\0';

    // Detect end
    if (ch == EOF && (i == 0 || ferror(fp)))
    {
        free(buffer);
        return NULL;
    }
    return buffer;
}

void lineByline(FILE * file){
char *s;
while ((s = readline(file, 0)) != NULL)
{
    puts(s);
    free(s);
    printf("\n");
}
}

int main()
{
    char *fileName = "input-1.txt";
    FILE* file = fopen(fileName, "r");
    lineByline(file);
    return 0;
}

1
Tại sao bạn sử dụng fgetcthay vì fgets?
ngọn lửa

1
const char *readLine(FILE *file, char* line) {

    if (file == NULL) {
        printf("Error: file pointer is null.");
        exit(1);
    }

    int maximumLineLength = 128;
    char *lineBuffer = (char *)malloc(sizeof(char) * maximumLineLength);

    if (lineBuffer == NULL) {
        printf("Error allocating memory for line buffer.");
        exit(1);
    }

    char ch = getc(file);
    int count = 0;

    while ((ch != '\n') && (ch != EOF)) {
        if (count == maximumLineLength) {
            maximumLineLength += 128;
            lineBuffer = realloc(lineBuffer, maximumLineLength);
            if (lineBuffer == NULL) {
                printf("Error reallocating space for line buffer.");
                exit(1);
            }
        }
        lineBuffer[count] = ch;
        count++;

        ch = getc(file);
    }

    lineBuffer[count] = '\0';
    char line[count + 1];
    strncpy(line, lineBuffer, (count + 1));
    free(lineBuffer);
    return line;

}


char linebuffer[256];
while (!feof(myFile)) {
    const char *line = readLine(myFile, linebuffer);
    printf("%s\n", line);
}

lưu ý rằng biến 'line' được khai báo trong hàm gọi và sau đó được truyền, vì vậy readLinehàm của bạn sẽ điền vào bộ đệm được xác định trước và chỉ trả về nó. Đây là cách mà hầu hết các thư viện C hoạt động.

Có nhiều cách khác mà tôi biết:

  • định nghĩa char line[]là tĩnh ( static char line[MAX_LINE_LENGTH] -> nó sẽ giữ giá trị SAU khi trả về từ hàm). -> xấu, chức năng không được phát lại và điều kiện cuộc đua có thể xảy ra -> nếu bạn gọi nó hai lần từ hai luồng, nó sẽ ghi đè lên kết quả của nó
  • malloc()ing dòng char [] và giải phóng nó trong các hàm gọi -> quá nhiều mallocs đắt tiền , và, giao trách nhiệm giải phóng bộ đệm cho một chức năng khác (giải pháp thanh lịch nhất là gọi mallocfreetrên bất kỳ bộ đệm nào trong cùng chức năng)

btw, 'rõ ràng' đúc từ char*đến const char*là dư thừa.

btw2, không cần malloc()lineBuffer, chỉ cần xác định nó char lineBuffer[128], vì vậy bạn không cần phải giải phóng nó

btw3 không sử dụng 'mảng ngăn xếp có kích thước động' (xác định mảng là char arrayName[some_nonconstant_variable]), nếu bạn không biết chính xác bạn đang làm gì, nó chỉ hoạt động trong C99.


1
lưu ý rằng biến 'line' được khai báo trong hàm gọi và sau đó được thông qua - có lẽ bạn đã xóa khai báo cục bộ của dòng trong hàm rồi. Ngoài ra, bạn cần cho biết chức năng của bộ đệm mà bạn sẽ vượt qua trong bao lâu và nghĩ ra một chiến lược xử lý các dòng quá dài cho bộ đệm bạn truyền vào.
JeremyP

1

Bạn nên sử dụng các hàm ANSI để đọc một dòng, vd. fget. Sau khi gọi, bạn cần free () trong ngữ cảnh gọi, vd:

...
const char *entirecontent=readLine(myFile);
puts(entirecontent);
free(entirecontent);
...

const char *readLine(FILE *file)
{
  char *lineBuffer=calloc(1,1), line[128];

  if ( !file || !lineBuffer )
  {
    fprintf(stderr,"an ErrorNo 1: ...");
    exit(1);
  }

  for(; fgets(line,sizeof line,file) ; strcat(lineBuffer,line) )
  {
    if( strchr(line,'\n') ) *strchr(line,'\n')=0;
    lineBuffer=realloc(lineBuffer,strlen(lineBuffer)+strlen(line)+1);
    if( !lineBuffer )
    {
      fprintf(stderr,"an ErrorNo 2: ...");
      exit(2);
    }
  }
  return lineBuffer;
}

1

Thực hiện phương pháp để đọc và nhận nội dung từ một tệp (input1.txt)

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

void testGetFile() {
    // open file
    FILE *fp = fopen("input1.txt", "r");
    size_t len = 255;
    // need malloc memory for line, if not, segmentation fault error will occurred.
    char *line = malloc(sizeof(char) * len);
    // check if file exist (and you can open it) or not
    if (fp == NULL) {
        printf("can open file input1.txt!");
        return;
    }
    while(fgets(line, len, fp) != NULL) {
        printf("%s\n", line);
    }
    free(line);
}

Hy vọng điều này giúp đỡ. Chúc mừng mã hóa!


0

Bạn mắc lỗi trả lại một con trỏ cho một biến tự động. Dòng biến được phân bổ trong ngăn xếp và chỉ sống miễn là hàm sống. Bạn không được phép trả lại một con trỏ cho nó, vì ngay khi nó trả về, bộ nhớ sẽ được cung cấp ở nơi khác.

const char* func x(){
    char line[100];
    return (const char*) line; //illegal
}

Để tránh điều này, bạn có thể trả về một con trỏ vào bộ nhớ nằm trong heap, vd. lineBuffer và trách nhiệm của người dùng là gọi miễn phí () khi anh ta hoàn thành nó. Ngoài ra, bạn có thể yêu cầu người dùng chuyển cho bạn làm đối số một địa chỉ bộ nhớ để ghi nội dung dòng tại đó.


Có một sự khác biệt giữa hành vi bất hợp pháp và không xác định ^^.
Phong

0

Tôi muốn một mã từ mặt đất 0 vì vậy tôi đã làm điều này để đọc nội dung của từng dòng từ điển.

char temp_str [20]; // bạn có thể thay đổi kích thước bộ đệm theo yêu cầu của bạn Và Độ dài của một dòng trong Tệp.

Lưu ý Tôi đã khởi tạo bộ đệm Với ký tự Null mỗi lần tôi đọc dòng. Hàm này có thể được Tự động hóa nhưng vì tôi cần Bằng chứng về Khái niệm và muốn thiết kế chương trình Byte By Byte

#include<stdio.h>

int main()
{
int i;
char temp_ch;
FILE *fp=fopen("data.txt","r");
while(temp_ch!=EOF)
{
 i=0;
  char temp_str[20]={'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0'};
while(temp_ch!='\n')
{
  temp_ch=fgetc(fp);
  temp_str[i]=temp_ch;
  i++;
}
if(temp_ch=='\n')
{
temp_ch=fgetc(fp);
temp_str[i]=temp_ch;
}
printf("%s",temp_str);
}
return 0;
}

chương trình của bạn sẽ làm việc nếu ngoặc của bạn đang ở đúng chỗ;) ví dụint main() {
dylnmc

Ngẫu nhiên, bạn không cần chỉ định tất cả 20 '\ 0'. Bạn chỉ có thể viết: codechar temp_str [20] = {'\ 0'}; code c sẽ tự động điền vào từng vị trí bằng một bộ kết thúc null vì cách khai báo mảng là nếu một mảng được khởi tạo với ít phần tử mà mảng chứa, phần tử cuối cùng sẽ điền vào các phần tử còn lại.
alaniane

Tôi tin rằng char temp_str[20] = {0}cũng lấp đầy toàn bộ mảng ký tự bằng các đầu cuối null.
Thu Yein Tun

0

Tôi thực hiện từ đầu:

FILE *pFile = fopen(your_file_path, "r");
int nbytes = 1024;
char *line = (char *) malloc(nbytes);
char *buf = (char *) malloc(nbytes);

size_t bytes_read;
int linesize = 0;
while (fgets(buf, nbytes, pFile) != NULL) {
    bytes_read = strlen(buf);
    // if line length larger than size of line buffer
    if (linesize + bytes_read > nbytes) {
        char *tmp = line;
        nbytes += nbytes / 2;
        line = (char *) malloc(nbytes);
        memcpy(line, tmp, linesize);
        free(tmp);
    }
    memcpy(line + linesize, buf, bytes_read);
    linesize += bytes_read;

    if (feof(pFile) || buf[bytes_read-1] == '\n') {
        handle_line(line);
        linesize = 0;
        memset(line, '\0', nbytes);
    }
}

free(buf);
free(line);

Tại sao bạn sử dụng heap (malloc) thay vì stack? Có vẻ như có một giải pháp dựa trên ngăn xếp đơn giản hơn với fgetsđiều đó có thể được sử dụng.
ngọn lửa

0

Cung cấp một getdelimchức năng di động và chung chung , thử nghiệm thông qua msvc, clang, gcc.

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

ssize_t
portabl_getdelim(char ** restrict linep,
                 size_t * restrict linecapp,
                 int delimiter,
                 FILE * restrict stream) {
    if (0 == *linep) {
        *linecapp = 8;
        *linep = malloc(*linecapp);
        if (0 == *linep) {
            return EOF;
        }
    }

    ssize_t linelen = 0;
    int c = 0;
    char *p = *linep;

    while (EOF != (c = fgetc(stream))) {
        if (linelen == (ssize_t) *linecapp - 1) {
            *linecapp <<= 1;
            char *p1 = realloc(*linep, *linecapp);
            if (0 == *p1) {
                return EOF;
            }
            p = p1 + linelen;
        }
        *p++ = c;
        linelen++;

        if (delimiter == c) {
            *p = 0;
            return linelen;
        }
    }
    return EOF == c ? EOF : linelen;
}


int
main(int argc, char **argv) {
    const char *filename = "/a/b/c.c";
    FILE *file = fopen(filename, "r");
    if (!file) {
        perror(filename);
        return 1;
    }

    char *line = 0;
    size_t linecap = 0;
    ssize_t linelen;

    while (0 < (linelen = portabl_getdelim(&line, &linecap, '\n', file))) {
        fwrite(line, linelen, 1, stdout);
    }
    if (line) {
        free(line);
    }
    fclose(file);   

    return 0;
}

Tại sao làm điều này khi fgetstồn tại?
ngọn lửa

fgets có thể tùy chỉnh các dấu phân cách dòng hoặc tùy chỉnh những gì cần làm về các dòng hiện tại?
南山

getdelimcho phép các dấu phân cách tùy chỉnh. Ngoài ra tôi cũng lưu ý rằng không có giới hạn độ dài dòng - trong trường hợp này bạn có thể sử dụng ngăn xếp với getline. (Cả hai được mô tả ở đây: man7.org/linux/man-pages/man3/getline.3.html )
theicfire

Bạn có nói về Linux không, câu hỏi là về cách đọc dòng trong C, phải không?
南山

Điều này hoạt động cho bất kỳ triển khai c tiêu chuẩn nào ( getdelimgetlineđã được chuẩn hóa trong POSIX.1-2008, một người khác đề cập trên trang này). fgetscũng là tiêu chuẩn c và không phải là Linux cụ thể
theicfire
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.