C membaca file baris demi baris


184

Saya menulis fungsi ini untuk membaca baris dari file:

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

Fungsi membaca file dengan benar, dan menggunakan printf saya melihat bahwa string constLine juga bisa dibaca dengan benar.

Namun, jika saya menggunakan fungsi misalnya seperti ini:

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

printf menampilkan omong kosong. Mengapa?


Gunakan fgetssebagai ganti fgetc. Anda membaca karakter demi karakter, bukannya baris demi baris.
Shiv

3
Perhatikan bahwa itu getline()adalah bagian dari POSIX 2008. Mungkin ada platform seperti POSIX tanpanya, terutama jika mereka tidak mendukung sisa POSIX 2008, tetapi dalam dunia sistem POSIX, getline()cukup portabel saat ini.
Jonathan Leffler

Jawaban:


305

Jika tugas Anda bukan untuk menciptakan fungsi membaca baris demi baris, tetapi hanya untuk membaca file baris demi baris, Anda dapat menggunakan potongan kode khas yang melibatkan getline()fungsi (lihat halaman manual di sini ):

#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
Itu tidak portabel.
JeremyP

16
Lebih tepatnya, ini getlinekhusus untuk GNU libc, yaitu ke Linux. Namun, jika tujuannya adalah untuk memiliki fungsi membaca garis (sebagai lawan dari belajar C), ada beberapa fungsi membaca garis domain publik yang tersedia di web.
Gilles 'SANGAT berhenti menjadi jahat'

11
Mengapa saya harus melakukan itu? Baca manual, buffer dialokasikan kembali pada setiap panggilan, maka harus dibebaskan pada akhirnya.
mbaitoff

29
The if(line)check adalah berlebihan. Memanggil free(NULL)pada dasarnya adalah larangan.
Agustus

50
Bagi mereka yang mengatakan bahwa getline ini khusus untuk GNU libc, "Keduanya getline () dan getdelim () pada awalnya adalah ekstensi GNU. Mereka distandarisasi dalam 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);

Bagi saya ini menghasilkan menimpa setiap baris dengan yang berikutnya. Lihat pertanyaan ini berdasarkan jawaban di atas.
Cezar Cobuz

5
Mengapa para pemain (FILE*) fp? Bukankah fpsudah FILE *dan juga fopen()mengembalikan FILE *?
Akuntan pada

1
Jika Anda setuju dengan garis yang dibatasi hingga panjang tertentu, ini adalah jawaban terbaik. Kalau tidak menggunakan getlineadalah alternatif yang baik. Saya setuju para FILE *pemeran tidak perlu.
theicfire

Saya menghapus gips yang tidak perlu, menambahkan variabel untuk panjang buffer dan berubah fpmenjadi filePointerlebih jelas.
Rob

21

Dalam readLinefungsi Anda, Anda mengembalikan pointer ke linearray (Sebenarnya, pointer ke karakter pertama, tetapi perbedaannya tidak relevan di sini). Karena ini adalah variabel otomatis (yaitu, "ada di tumpukan"), memori tersebut akan diperoleh kembali saat fungsi kembali. Anda melihat omong kosong karena printftelah meletakkan barang-barangnya sendiri di tumpukan.

Anda perlu mengembalikan buffer yang dialokasikan secara dinamis dari fungsi. Anda sudah memilikinya, itu lineBuffer; yang harus Anda lakukan adalah memotongnya sesuai panjang yang diinginkan.

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

TAMBAH (respons terhadap pertanyaan tindak lanjut dalam komentar): readLinemengembalikan penunjuk ke karakter yang membentuk garis. Pointer ini adalah apa yang Anda butuhkan untuk bekerja dengan isi baris. Ini juga yang harus Anda lewati freeketika Anda selesai menggunakan memori yang diambil oleh karakter-karakter ini. Inilah cara Anda menggunakan readLinefungsi ini:

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). */

@ Besi: Saya telah menambahkan sesuatu ke jawaban saya, tetapi saya tidak yakin apa kesulitan Anda sehingga mungkin melenceng.
Gilles 'SANGAT berhenti menjadi jahat'

@ Besi: jawabannya adalah Anda tidak membebaskannya. Anda mendokumentasikan (dalam dokumentasi API) fakta bahwa buffer yang dikembalikan adalah malloc'd ansd perlu dibebaskan oleh pemanggil. Maka orang-orang yang menggunakan fungsi readLine Anda akan (mudah-mudahan!) Menulis kode yang mirip dengan cuplikan yang ditambahkan Gilles ke jawabannya.
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
Ada beberapa masalah dengan kode ini: fopen_smembuat kode tidak dapat diakses. printfakan mencari penentu format dan tidak mencetak tanda persen dan karakter berikut seperti apa adanya . Null byte akan membuat semua karakter di sisa baris menghilang. (Jangan bilang byte nol tidak bisa terjadi!)
hagello

Dan omong-omong, Anda tidak memecahkan masalah. OP menjelaskan bahwa nilai kembali fungsinya menghilang. Saya tidak melihat Anda mengatasi masalah ini.
hagello

@ Hartley Saya tahu ini adalah komentar yang lebih lama, tetapi saya menambahkan ini agar seseorang tidak membaca komentarnya dan mencoba untuk membebaskan (baris) dalam loop. Memori untuk saluran hanya dialokasikan satu kali sebelum loop dimulai, jadi itu hanya boleh gratis satu kali setelah loop berakhir. Jika Anda mencoba membebaskan garis di dalam loop, Anda akan mendapatkan hasil yang tidak terduga. Bergantung pada seberapa bebas () memperlakukan pointer. Jika hanya membatalkan memori dan membiarkan pointer menunjuk ke lokasi lama, kode dapat berfungsi. Jika itu memberikan nilai lain ke pointer maka Anda akan menimpa bagian memori yang berbeda.
alaniane

2
printf (line) salah! Jangan lakukan ini. Ini membuka kode Anda ke kerentanan format string di mana Anda dapat dengan bebas membaca / menulis langsung ke memori melalui barang yang sedang dicetak. Jika saya meletakkan% n /% p dalam file dan mengarahkan pointer kembali ke alamat dalam memori (dalam string dari file) yang saya kendalikan, saya bisa menjalankan kode itu.
oxagast

10

readLine() mengembalikan pointer ke variabel lokal, yang menyebabkan perilaku tidak terdefinisi.

Untuk berkeliling Anda bisa:

  1. Buat variabel dalam fungsi pemanggil dan berikan alamatnya readLine()
  2. Alokasikan memori untuk linemenggunakan malloc()- dalam hal ini lineakan tetap ada
  3. Gunakan variabel global, meskipun itu umumnya praktik yang buruk


4

Beberapa hal salah dengan contoh:

  • Anda lupa menambahkan \ n ke printfs Anda. Juga pesan kesalahan harus pergi ke stderr yaitufprintf(stderr, ....
  • (bukan biggy tapi) pertimbangkan menggunakan fgetc()daripada getc(). getc()adalah makro, fgetc()adalah fungsi yang tepat
  • getc()mengembalikan suatu intsehingga chharus dinyatakan sebagai int. Ini penting karena perbandingan dengan EOFakan ditangani dengan benar. Beberapa set karakter 8 bit digunakan 0xFFsebagai karakter yang valid (ISO-LATIN-1 akan menjadi contoh) dan EOFyang -1, akan 0xFFjika ditugaskan kechar .
  • Ada potensi buffer overflow di baris

    lineBuffer[count] = '\0';

    Jika panjang baris tepat 128 karakter, countberarti 128 pada titik yang dieksekusi.

  • Seperti yang telah ditunjukkan orang lain, lineadalah array yang dinyatakan secara lokal. Anda tidak dapat mengembalikan pointer ke sana.

  • strncpy(count + 1)akan menyalin paling count + 1karakter tetapi akan berakhir jika hits '\0' Karena Anda mengatur lineBuffer[count]untuk '\0'Anda tahu itu tidak akan pernah sampai ke count + 1. Namun, jika itu terjadi, itu tidak akan mengakhiri '\0', jadi Anda perlu melakukannya. Anda sering melihat sesuatu seperti berikut:

    char buffer [BUFFER_SIZE];
    strncpy(buffer, sourceString, BUFFER_SIZE - 1);
    buffer[BUFFER_SIZE - 1] = '\0';
  • jika Anda malloc()ingin mengembalikan sebuah baris (sebagai pengganti chararray lokal Anda ), jenis return Anda seharusnya char*- drop the 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);
    }
}

bagaimana dengan yang ini?


2

Inilah beberapa jam saya ... Membaca seluruh file baris demi baris.

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
Mengapa Anda menggunakan fgetcbukan fgets?
theicfire

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

perhatikan bahwa variabel 'line' dideklarasikan dalam fungsi panggilan dan kemudian diteruskan, jadi readLinefungsi Anda mengisi buffer yang telah ditentukan dan mengembalikannya. Ini adalah cara sebagian besar perpustakaan C bekerja.

Ada beberapa cara lain, yang saya ketahui:

  • mendefinisikan char line[]sebagai statis ( static char line[MAX_LINE_LENGTH] -> itu akan menahan nilainya SETELAH kembali dari fungsi). -> buruk, fungsinya tidak reentrant, dan kondisi balapan dapat terjadi -> jika Anda menyebutnya dua kali dari dua utas, itu akan menimpa hasilnya
  • malloc()ing the char line [], dan membebaskannya dalam fungsi panggil -> terlalu banyak yang mahal malloc, dan, mendelegasikan tanggung jawab untuk membebaskan buffer ke fungsi lain (solusi paling elegan adalah memanggil mallocdan freepada setiap buffer dalam fungsi yang sama)

btw, casting 'eksplisit' dari char*to const char*redundant.

btw2, tidak perlu ke malloc()lineBuffer, cukup tentukan saja char lineBuffer[128], jadi Anda tidak perlu membebaskannya

btw3 tidak menggunakan 'array stack ukuran dinamis' (mendefinisikan array sebagai char arrayName[some_nonconstant_variable] ), jika Anda tidak tahu persis apa yang Anda lakukan, ia hanya bekerja di C99.


1
perhatikan bahwa variabel 'baris' dideklarasikan dalam fungsi panggilan dan kemudian diteruskan, - Anda mungkin harus menghapus deklarasi garis lokal di fungsi itu. Selain itu, Anda perlu memberi tahu fungsi berapa lama buffer yang Anda lewati dan pikirkan strategi untuk menangani garis yang terlalu lama untuk buffer yang Anda lewati.
JeremyP

1

Anda harus menggunakan fungsi ANSI untuk membaca baris, mis. uang. Setelah menelepon, Anda perlu bebas () dalam konteks panggilan, misalnya:

...
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

Terapkan metode untuk membaca, dan dapatkan konten dari file (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);
}

Semoga bantuan ini. Selamat coding!


0

Anda membuat kesalahan dengan mengembalikan pointer ke variabel otomatis. Baris variabel dialokasikan dalam tumpukan dan hanya hidup selama fungsi tersebut hidup. Anda tidak diperbolehkan untuk mengembalikan pointer ke sana, karena segera setelah mengembalikan memori akan diberikan di tempat lain.

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

Untuk menghindarinya, Anda harus mengembalikan pointer ke memori yang berada di heap eg. lineBuffer dan seharusnya menjadi tanggung jawab pengguna untuk menelepon gratis () ketika ia selesai dengan itu. Sebagai alternatif, Anda dapat meminta pengguna untuk menyampaikan kepada Anda argumen alamat memori tempat Anda menulis isi baris.


Ada perbedaan antara perilaku ilegal dan tidak jelas ^^.
Phong

0

Saya ingin kode dari ground 0 jadi saya melakukan ini untuk membaca isi kata demi kata kamus demi baris.

char temp_str [20]; // Anda dapat mengubah ukuran buffer sesuai dengan kebutuhan Anda dan panjang satu baris dalam File.

Catatan Saya sudah menginisialisasi penyangga Dengan karakter Null setiap kali saya membaca baris. Fungsi ini dapat diotomatisasi, tetapi karena saya membutuhkan bukti konsep dan ingin merancang program 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;
}

program Anda akan bekerja jika tanda kurung berada di tempat yang tepat;) misalnyaint main() {
dylnmc

Kebetulan, Anda tidak perlu menentukan semua 20 '\ 0'. Anda bisa menulis: codechar temp_str [20] = {'\ 0'}; code c akan secara otomatis mengisi setiap slot dengan terminator nol karena cara deklarasi array berfungsi adalah jika array diinisialisasi dengan lebih sedikit elemen yang terdapat pada array, elemen terakhir akan mengisi elemen yang tersisa.
alaniane

Saya percaya char temp_str[20] = {0}juga mengisi seluruh array karakter dengan terminator nol.
Thu Yein Tun

0

Alat saya dari awal:

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

Mengapa Anda menggunakan heap (malloc) bukan tumpukan? Tampaknya ada solusi berbasis tumpukan sederhana dengan fgetsyang dapat digunakan.
theicfire

0

Menyediakan fungsi portabel dan generik getdelim, uji lulus melalui 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;
}

Mengapa ini fgetsada?
theicfire

apakah anggaran dapat menyesuaikan pembatas garis atau menyesuaikan apa yang harus dilakukan tentang baris saat ini?
南山 竹

getdelimmemungkinkan untuk pembatas yang disesuaikan. Juga saya perhatikan tidak memiliki batas panjang garis - dalam hal ini Anda dapat menggunakan stack dengan getline. (Keduanya diuraikan di sini: man7.org/linux/man-pages/man3/getline.3.html )
theicfire

Anda berbicara tentang Linux saja, pertanyaannya adalah tentang bagaimana membaca baris dalam C, kan?
南山 竹

Ini berfungsi untuk setiap implementasi standar c ( getdelimdan getlinetelah distandarisasi dalam POSIX.1-2008, orang lain menyebutkan pada halaman ini). fgetsjuga standar c, dan bukan spesifik linux
theicfire
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.