C Datei zeilenweise lesen


183

Ich habe diese Funktion geschrieben, um eine Zeile aus einer Datei zu lesen:

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

Die Funktion liest die Datei korrekt und mit printf sehe ich, dass die constLine-Zeichenfolge auch korrekt gelesen wurde.

Wenn ich jedoch die Funktion zB so benutze:

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

printf gibt Kauderwelsch aus. Warum?


Verwenden Sie fgetsanstelle von fgetc. Sie lesen Zeichen für Zeichen anstatt Zeile für Zeile.
Shiv

3
Beachten Sie, dass dies getline()ein Teil von POSIX 2008 ist. Es kann POSIX-ähnliche Plattformen ohne POSIX 2008 geben, insbesondere wenn sie den Rest von POSIX 2008 nicht unterstützen, aber in der Welt der POSIX-Systeme getline()heutzutage ziemlich portabel sind.
Jonathan Leffler

Antworten:


304

Wenn Ihre Aufgabe nicht darin besteht, die zeilenweise Lesefunktion zu erfinden, sondern nur die Datei zeilenweise zu lesen, können Sie ein typisches Codefragment verwenden, das die getline()Funktion umfasst (siehe Handbuchseite hier ):

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

82
Das ist nicht tragbar.
JeremyP

16
Genauer gesagt ist dies getlinespezifisch für GNU libc, dh für Linux. Wenn jedoch eine Zeilenlesefunktion (im Gegensatz zum Lernen von C) vorgesehen ist, stehen im Web mehrere gemeinfreie Zeilenlesefunktionen zur Verfügung.
Gilles 'SO - hör auf böse zu sein'

11
Warum sollte ich das tun? Lesen Sie das Handbuch, der Puffer wird bei jedem Aufruf neu zugewiesen und sollte am Ende freigegeben werden.
Mbaitoff

29
Der if(line)Scheck ist überflüssig. Anrufen free(NULL)ist im Wesentlichen ein No-Op.
aroth

49
Für diejenigen, die sagten, dass diese getline spezifisch für GNU libc ist, "waren sowohl getline () als auch getdelim () ursprünglich GNU-Erweiterungen. Sie wurden in POSIX.1-2008 standardisiert."
Willkill07

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

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

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

fclose(filePointer);

Für mich führt dies dazu, dass jede Zeile mit der nächsten überschrieben wird. Siehe diese Frage basierend auf der obigen Antwort.
Cezar Cobuz

5
Warum die Besetzung (FILE*) fp? Ist das nicht fpschon ein FILE *und gibt auch ein fopen()zurück FILE *?
Buchhalter م

1
Wenn Sie damit einverstanden sind, dass Linien auf eine bestimmte Länge begrenzt sind, ist dies die beste Antwort. Ansonsten ist die Verwendung getlineeine gute Alternative. Ich bin damit einverstanden, dass die FILE *Besetzung unnötig ist.
Theicfire

I entfernt , um den nicht-notwendige Guss, hinzugefügt , um eine Variable für die Pufferlänge und geändert , fpum filePointerfür mehr Klarheit.
Rob

21

In Ihrer readLineFunktion geben Sie einen Zeiger auf das lineArray zurück (genau genommen einen Zeiger auf das erste Zeichen, aber der Unterschied ist hier irrelevant). Da es sich um eine automatische Variable handelt (dh sich auf dem Stapel befindet), wird der Speicher bei der Rückkehr der Funktion zurückgefordert. Sie sehen Kauderwelsch, weil printfer seine eigenen Sachen auf den Stapel gelegt hat.

Sie müssen einen dynamisch zugewiesenen Puffer von der Funktion zurückgeben. Du hast schon einen, es ist lineBuffer; Alles, was Sie tun müssen, ist es auf die gewünschte Länge zu kürzen.

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

ADDED (Antwort auf die Folgefrage im Kommentar): GibtreadLine einen Zeiger auf die Zeichen zurück, aus denen die Zeile besteht. Mit diesem Zeiger müssen Sie mit dem Inhalt der Zeile arbeiten. Es ist auch das, woran Sie übergeben müssen, freewenn Sie den Speicher dieser Zeichen nicht mehr verwenden. So können Sie die readLineFunktion verwenden:

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: Ich habe meiner Antwort etwas hinzugefügt, bin mir aber nicht sicher, was Ihre Schwierigkeit ist, so dass es möglicherweise daneben liegt.
Gilles 'SO - hör auf böse zu sein'

@Iron: Die Antwort ist, dass Sie es nicht befreien. Sie dokumentieren (in der API-Dokumentation), dass der zurückgegebene Puffer malloc'd ansd ist und vom Aufrufer freigegeben werden muss. Dann schreiben Leute, die Ihre readLine-Funktion verwenden (hoffentlich!) Code, der dem Snippet ähnelt, das Gilles seiner Antwort hinzugefügt hat.
JeremyP

14
//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
Es gibt einige Probleme mit diesem Code: fopen_sDer Code ist nicht portierbar. printfsucht nach Formatspezifizierern und druckt keine Prozentzeichen und die folgenden Zeichen wie sie sind . Null-Bytes lassen alle Zeichen in der restlichen Zeile verschwinden. (Sagen Sie mir nicht, dass keine Null-Bytes passieren können!)
Hagello

Übrigens lösen Sie das Problem nicht. Das OP beschreibt, dass der Rückgabewert seiner Funktion verschwindet. Ich sehe nicht, dass Sie dieses Problem ansprechen.
Hagello

@ Hartley Ich weiß, dass dies ein älterer Kommentar ist, aber ich füge ihn hinzu, damit jemand seinen Kommentar nicht liest und versucht, die Zeile freizugeben. Der Speicher für die Leitung wird nur einmal zugewiesen, bevor die Schleife beginnt, daher sollte er nur einmal frei sein, nachdem die Schleife endet. Wenn Sie versuchen, die Linie innerhalb der Schleife freizugeben, erhalten Sie unerwartete Ergebnisse. Abhängig davon, wie free () den Zeiger behandelt. Wenn nur der Speicher freigegeben wird und der Zeiger auf die alte Position zeigt, funktioniert der Code möglicherweise. Wenn dem Zeiger ein anderer Wert zugewiesen wird, überschreiben Sie einen anderen Speicherbereich.
Alaniane

2
printf (Zeile) ist falsch! Mach das nicht. Dies öffnet Ihren Code für eine Sicherheitsanfälligkeit im Zeichenfolgenformat, bei der Sie über das zu druckende Material frei direkt in den Speicher lesen / schreiben können. Wenn ich% n /% p in die Datei einfügen und den Zeiger auf eine Adresse im Speicher (in der Zeichenfolge aus der Datei) zurückführen würde, die ich gesteuert habe, könnte ich diesen Code ausführen.
Oxagast

10

readLine() Gibt den Zeiger auf die lokale Variable zurück, was zu undefiniertem Verhalten führt.

Um herumzukommen, können Sie:

  1. Erstellen Sie eine Variable in der Aufruferfunktion und übergeben Sie ihre Adresse an readLine()
  2. Ordnen Sie Speicher für die lineVerwendung zu malloc()- in diesem Fall linebleibt er bestehen
  3. Verwenden Sie eine globale Variable, obwohl dies im Allgemeinen eine schlechte Praxis ist


4

Einige Dinge stimmen mit dem Beispiel nicht:

  • Sie haben vergessen, \ n zu Ihren printfs hinzuzufügen. Auch Fehlermeldungen sollten an stderr gehen, dhfprintf(stderr, ....
  • (kein großes Problem, aber) erwägen, fgetc()eher als zu verwenden getc(). getc()ist ein Makro, fgetc()ist eine richtige Funktion
  • getc()gibt ein intso zurück chsollte als deklariert werden int. Dies ist wichtig, da der Vergleich mit EOFkorrekt behandelt wird. Einige 8-Bit-Zeichensätze werden 0xFFals gültiges Zeichen verwendet (ISO-LATIN-1 wäre ein Beispiel) und EOFdas ist -1, 0xFFwenn es a zugewiesen wird char.
  • An der Leitung liegt ein potenzieller Pufferüberlauf vor

    lineBuffer[count] = '\0';

    Wenn die Zeile genau 128 Zeichen lang ist, countist sie an dem Punkt, an dem sie ausgeführt wird, 128.

  • Wie andere bereits betont haben, linehandelt es sich um ein lokal deklariertes Array. Sie können keinen Zeiger darauf zurückgeben.

  • strncpy(count + 1)kopiert höchstens count + 1Zeichen, endet jedoch, wenn es trifft. '\0' Weil Sie eingestellt haben lineBuffer[count], '\0'wissen Sie, dass es niemals erreicht wird count + 1. Wenn dies jedoch der Fall wäre, würde keine Kündigung '\0'aktiviert, sodass Sie dies tun müssen. Sie sehen oft Folgendes:

    char buffer [BUFFER_SIZE];
    strncpy(buffer, sourceString, BUFFER_SIZE - 1);
    buffer[BUFFER_SIZE - 1] = '\0';
  • Wenn Sie malloc()eine Zeile zurückgeben möchten (anstelle Ihres lokalen charArrays), sollte Ihr Rückgabetyp sein: char*- Löschen Sie die 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);
    }
}

was ist mit diesem?


2

Hier sind meine mehreren Stunden ... Lesen der gesamten Datei Zeile für Zeile.

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
Warum benutzt du fgetcstatt 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);
}

Beachten Sie, dass die Variable 'line' in der aufrufenden Funktion deklariert und dann übergeben wird, sodass Ihre readLineFunktion den vordefinierten Puffer füllt und ihn nur zurückgibt. So funktionieren die meisten C-Bibliotheken.

Es gibt andere Möglichkeiten, die mir bekannt sind:

  • Definieren des char line[]als statisch ( static char line[MAX_LINE_LENGTH] -> es behält seinen Wert NACH der Rückkehr von der Funktion). -> schlecht, die Funktion ist nicht wiedereintrittsfähig und es kann zu einer Racebedingung kommen -> Wenn Sie sie zweimal von zwei Threads aus aufrufen, werden die Ergebnisse überschrieben
  • malloc()die char-Zeile [] freigeben und beim Aufrufen von Funktionen freigeben -> zu viele teure mallocs, und die Verantwortung für das Freigeben des Puffers an eine andere Funktion delegieren (die eleganteste Lösung besteht darin, Puffer in derselben Funktion aufzurufen mallocund freeeinzuschalten).

Übrigens ist 'explizites' Casting von char*bis const char*überflüssig.

Übrigens ist malloc()der lineBuffer nicht erforderlich. Definieren Sie ihn einfach char lineBuffer[128], damit Sie ihn nicht freigeben müssen

Übrigens verwenden Sie keine Stapelstapel mit dynamischer Größe (definieren Sie das Array als char arrayName[some_nonconstant_variable]). Wenn Sie nicht genau wissen, was Sie tun, funktioniert dies nur in C99.


1
Beachten Sie, dass die Variable 'line' in der aufrufenden Funktion deklariert und dann übergeben wird. - Sie sollten dann wahrscheinlich die lokale Deklaration der Zeile in der Funktion gelöscht haben. Außerdem müssen Sie der Funktion mitteilen, wie lang der Puffer ist, den Sie übergeben, und sich eine Strategie für die Behandlung von Zeilen
überlegen

1

Sie sollten die ANSI-Funktionen zum Lesen einer Zeile verwenden, z. fgets. Nach dem Aufruf benötigen Sie free () im Aufrufkontext, zB:

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

Implementieren Sie eine Methode zum Lesen und Abrufen von Inhalten aus einer Datei (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);
}

Ich hoffe das hilft. Viel Spaß beim Codieren!


0

Sie machen den Fehler, einen Zeiger auf eine automatische Variable zurückzugeben. Die variable Zeile wird im Stapel zugewiesen und lebt nur so lange, wie die Funktion lebt. Sie dürfen keinen Zeiger darauf zurückgeben, da der Speicher bei der Rückgabe an anderer Stelle angegeben wird.

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

Um dies zu vermeiden, geben Sie entweder einen Zeiger auf den Speicher zurück, der sich auf dem Heap befindet, z. lineBuffer und es sollte in der Verantwortung des Benutzers liegen, free () aufzurufen, wenn er damit fertig ist. Alternativ können Sie den Benutzer bitten, Ihnen als Argument eine Speicheradresse zu übergeben, auf die der Zeileninhalt geschrieben werden soll.


Es gibt einen Unterschied zwischen illegalem und undefiniertem Verhalten ^^.
Phong

0

Ich möchte einen Code von Grund 0, also habe ich dies getan, um den Inhalt des Wörterbuchs Zeile für Zeile zu lesen.

char temp_str [20]; // Sie können die Puffergröße entsprechend Ihren Anforderungen und der Länge einer einzelnen Zeile in einer Datei ändern.

Hinweis Ich habe den Puffer jedes Mal, wenn ich eine Zeile lese, mit Nullzeichen initialisiert. Diese Funktion kann automatisiert werden, aber da ich einen Proof of Concept benötige und ein Programm Byte für Byte entwerfen möchte

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

Ihr Programm würde funktionieren, wenn Ihre Klammern an den richtigen Stellen wären;) zBint main() {
dylnmc

Übrigens müssen Sie nicht alle 20 '\ 0' angeben. Sie können einfach schreiben: codechar temp_str [20] = {'\ 0'}; code c füllt automatisch jeden Slot mit einem Null-Terminator, da Array-Deklarationen so funktionieren, dass das letzte Element die verbleibenden Elemente ausfüllt, wenn ein Array mit weniger Elementen initialisiert wird, die das Array enthält.
Alaniane

Ich glaube, char temp_str[20] = {0}füllt auch das gesamte Zeichenarray mit Nullterminatoren.
Do Yein Tun

0

Mein Gerät von Grund auf neu:

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

Warum verwenden Sie den Heap (Malloc) anstelle des Stapels? Es scheint, dass es eine einfachere stapelbasierte Lösung gibt fgets, die verwendet werden könnte.
Theicfire

0

Stellen Sie eine tragbare und generische getdelimFunktion bereit, Test bestanden über 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;
}

Warum tun, wenn es fgetsexistiert?
Theicfire

Können Fgets Zeilenbegrenzer anpassen oder anpassen, was mit aktuellen Zeilen zu tun ist?

getdelimermöglicht benutzerdefinierte Trennzeichen. Außerdem fällt mir auf, dass es keine Zeilenlängenbeschränkung gibt - in diesem Fall können Sie den Stapel mit verwenden getline. (Beide hier beschrieben: man7.org/linux/man-pages/man3/getline.3.html )
Theicfire

Sprechen Sie nur über Linux, die Frage ist, wie man Zeilen in C liest, richtig?

Dies funktioniert für jede Standard-C-Implementierung ( getdelimund getlinewurde in POSIX.1-2008 standardisiert, was auf dieser Seite von einer anderen Person erwähnt wird). fgetsist auch Standard c und nicht Linux-spezifisch
Theicfire
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.