C czyta plik linia po linii


184

Napisałem tę funkcję, aby odczytać wiersz z pliku:

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

Funkcja poprawnie odczytuje plik i za pomocą printf widzę, że łańcuch constLine również został poprawnie odczytany.

Jeśli jednak użyję funkcji np. Tak:

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

printf wydaje bełkot. Czemu?


Użyj fgetszamiast fgetc. Czytasz znak po znaku zamiast linii po linii.
Shiv

3
Należy pamiętać, że getline()jest to część POSIX 2008. Bez niej mogą istnieć platformy podobne do POSIX, szczególnie jeśli nie obsługują reszty POSIX 2008, ale w świecie systemów POSIX getline()są w dzisiejszych czasach dość przenośne.
Jonathan Leffler,

Odpowiedzi:


304

Jeśli Twoim zadaniem nie jest wymyślenie funkcji czytania wiersz po wierszu, ale po prostu odczytanie pliku wiersz po wierszu, możesz użyć typowego fragmentu kodu obejmującego tę getline()funkcję (patrz strona podręcznika tutaj ):

#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
To nie jest przenośne.
JeremyP,

16
Dokładniej, getlinejest to specyficzne dla GNU libc, tj. Dla Linuksa. Jeśli jednak intencją jest posiadanie funkcji czytania linii (w przeciwieństwie do nauki C), w Internecie dostępnych jest kilka funkcji czytania linii publicznych.
Gilles 'SO - przestań być zły'

11
Dlaczego powinienem to zrobić? Przeczytaj instrukcję, bufor jest ponownie przydzielany przy każdym połączeniu, a następnie powinien zostać zwolniony na końcu.
mbaitoff

29
if(line)Wyboru jest zbędne. Dzwonienie free(NULL)to w zasadzie brak możliwości.
aroth

50
Dla tych, którzy powiedzieli, że ten getline jest specyficzny dla GNU libc, „Zarówno getline (), jak i getdelim () były pierwotnie rozszerzeniami GNU. Zostały one znormalizowane w 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);

Dla mnie powoduje to zastąpienie każdej linii kolejną. Zobacz to pytanie w oparciu o powyższą odpowiedź.
Cezar Cobuz

5
Dlaczego obsada (FILE*) fp? Czy fpjuż nie jest a FILE *i fopen()zwraca również FILE *?
Księgowa

1
Jeśli nie masz nic przeciwko ograniczeniu linii do określonej długości, jest to najlepsza odpowiedź. W przeciwnym razie używanie getlinejest dobrą alternatywą. Zgadzam się, że FILE *obsada jest niepotrzebna.
theicfire

Usunąłem niepotrzebną obsadę, dodałem zmienną dla długości bufora i zmieniłem fpna filePointerdla większej przejrzystości.
Rob

21

W swojej readLinefunkcji zwracasz wskaźnik do linetablicy (Ściśle mówiąc, wskaźnik do jego pierwszego znaku, ale różnica nie ma tutaj znaczenia). Ponieważ jest to zmienna automatyczna (tzn. „Jest na stosie”), pamięć jest odzyskiwana po powrocie funkcji. Widzisz bełkot, ponieważ printfumieścił własne rzeczy na stosie.

Musisz zwrócić dynamicznie przydzielony bufor z funkcji. Już masz, to jest lineBuffer; wszystko, co musisz zrobić, to przyciąć go do pożądanej długości.

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

DODANO (odpowiedź na pytanie uzupełniające w komentarzu): readLinezwraca wskaźnik do znaków tworzących linię. Ten wskaźnik jest tym, czego potrzebujesz do pracy z zawartością linii. Jest to również to, do czego musisz przejść, freekiedy skończysz korzystać z pamięci zajmowanej przez te postacie. Oto jak możesz użyć readLinefunkcji:

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: Dodałem coś do mojej odpowiedzi, ale nie jestem pewien, jaka jest twoja trudność, więc może być nie na miejscu.
Gilles 'SO - przestań być zły'

@Iron: odpowiedź jest taka, że ​​go nie zwalniasz. Udokumentujesz (w dokumentacji API) fakt, że zwrócony bufor jest malloc'd ansd, a osoba dzwoniąca musi go zwolnić. Wtedy osoby korzystające z funkcji readLine (mam nadzieję!) Napiszą kod podobny do fragmentu kodu, który Gilles dodał do swojej odpowiedzi.
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
Istnieją pewne problemy z tym kodem: fopen_suniemożliwiają importowanie kodu. printfwyszuka specyfikatory formatu i nie wydrukuje znaków procentu oraz następujących znaków, jakimi są . Brak bajtów spowoduje zniknięcie wszystkich znaków w pozostałej części wiersza. (Nie mówcie, że bajty zerowe nie mogą się zdarzyć!)
Hagello,

Nawiasem mówiąc, nie rozwiązujesz problemu. OP opisuje, że wartość zwracana przez jego funkcję znika. Nie widzę, że rozwiązujesz ten problem.
hagello

@ Hartley Wiem, że to starszy komentarz, ale dodaję go, aby ktoś nie czytał jego komentarza i nie próbował uwolnić (linii) w pętli. Pamięć linii jest przydzielana tylko raz przed rozpoczęciem pętli, więc powinna być wolna tylko raz po zakończeniu pętli. Jeśli spróbujesz zwolnić linię w pętli, otrzymasz nieoczekiwane rezultaty. W zależności od tego, jak free () traktuje wskaźnik. Jeśli po prostu zwolni pamięć i pozostawi wskaźnik wskazujący na starą lokalizację, kod może działać. Jeśli przypisze inną wartość do wskaźnika, zastąpisz inną sekcję pamięci.
alaniane

2
printf (linia) jest błędna! Nie rób tego. To otwiera twój kod na lukę w formacie łańcucha, w której możesz swobodnie czytać / zapisywać bezpośrednio do pamięci za pomocą drukowanych materiałów. Gdybym włożył% n /% p do pliku i skierował wskaźnik z powrotem na adres w pamięci (w ciągu z pliku), który kontrolowałem, mógłbym wykonać ten kod.
oxagast

10

readLine() zwraca wskaźnik do zmiennej lokalnej, co powoduje niezdefiniowane zachowanie.

Aby się obejść, możesz:

  1. Utwórz zmienną w funkcji dzwoniącego i przekaż jej adres readLine()
  2. Przydziel pamięć do lineużycia malloc()- w tym przypadku linebędzie trwała
  3. Użyj zmiennej globalnej, chociaż jest to ogólnie zła praktyka


4

Niektóre rzeczy są złe w przykładzie:

  • zapomniałeś dodać \ n do swoich printfs. Również komunikaty o błędach powinny iść do stderr tjfprintf(stderr, ....
  • (nie duży, ale) rozważ użycie fgetc()zamiast getc(). getc()jest makrem, fgetc()jest właściwą funkcją
  • getc()zwraca a intwięc chnależy zadeklarować jako int. Jest to ważne, ponieważ porównanie z EOFbędzie obsługiwane poprawnie. Niektóre 8-bitowe zestawy znaków używają 0xFFjako prawidłowych znaków (na przykład ISO-LATIN-1), a EOFktóre wynoszą -1, zostaną 0xFFprzypisane do char.
  • Istnieje potencjalne przepełnienie bufora na linii

    lineBuffer[count] = '\0';

    Jeśli linia ma dokładnie 128 znaków, countw punkcie, który zostanie wykonany, ma 128 znaków .

  • Jak zauważyli inni, linejest to tablica deklarowana lokalnie. Nie możesz zwrócić do niego wskaźnika.

  • strncpy(count + 1)skopiuje w większości count + 1znaków, ale zakończy jeśli trafi '\0' Ponieważ zestaw lineBuffer[count]do '\0'wiesz, że nigdy nie dostanie się count + 1. Jeśli jednak tak się stanie, nie spowoduje zakończenia '\0', więc musisz to zrobić. Często widzisz coś takiego:

    char buffer [BUFFER_SIZE];
    strncpy(buffer, sourceString, BUFFER_SIZE - 1);
    buffer[BUFFER_SIZE - 1] = '\0';
  • jeśli masz malloc()wiersz do zwrócenia (zamiast chartablicy lokalnej ), typem zwrotu powinno być char*- upuść 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);
    }
}

a co z tym?


2

Oto moje kilka godzin ... Czytanie całego pliku linia po linii.

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
Dlaczego używasz fgetczamiast 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);
}

zwróć uwagę, że zmienna „line” jest deklarowana w funkcji wywołującej, a następnie przekazywana, więc twoja readLinefunkcja wypełnia predefiniowany bufor i po prostu zwraca go. W ten sposób działa większość bibliotek C.

Są inne sposoby, o których jestem świadomy:

  • zdefiniowanie char line[]jako statyczne ( static char line[MAX_LINE_LENGTH] -> zachowa jego wartość PO powrocie z funkcji). -> źle, funkcja nie jest ponownie wysyłana i może wystąpić wyścig -> jeśli wywołasz ją dwa razy z dwóch wątków, zastąpi to wyniki
  • malloc()wprowadzenie linii char [] i zwolnienie jej w wywoływaniu funkcji -> zbyt wiele drogich mallocs oraz delegowanie odpowiedzialności za zwolnienie bufora do innej funkcji (najbardziej eleganckim rozwiązaniem jest wywołanie malloci freedowolne bufory w tej samej funkcji)

btw, „jawne” rzutowanie z char*na na const char*jest zbędne.

btw2, nie ma potrzeby malloc()lineBuffer, po prostu zdefiniuj go char lineBuffer[128], więc nie musisz go zwalniać

btw3 nie używa „dynamicznych tablic stosów” (definiujących tablicę jako char arrayName[some_nonconstant_variable]), jeśli nie wiesz dokładnie, co robisz, działa tylko w C99.


1
zwróć uwagę, że zmienna „linia” jest deklarowana w funkcji wywołującej, a następnie przekazywana, - prawdopodobnie wtedy powinieneś usunąć lokalną deklarację linii w funkcji Musisz także powiedzieć funkcji, jak długo bufor jest przekazywany, i wymyślić strategię obsługi linii, które są zbyt długie dla bufora, który przekazujesz.
JeremyP

1

Powinieneś używać funkcji ANSI do czytania linii, np. fgets. Po wywołaniu potrzebujesz free () w kontekście wywołania, np .:

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

Wdrożenie metody odczytu i pobierania zawartości z pliku (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);
}

Mam nadzieję, że to pomoże. Miłego kodowania!


0

Popełniasz błąd zwracając wskaźnik do zmiennej automatycznej. Linia zmienna jest przydzielona na stosie i trwa tylko tak długo, jak długo trwa funkcja. Nie możesz zwrócić do niego wskaźnika, ponieważ jak tylko on zwróci, pamięć zostanie podana gdzie indziej.

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

Aby tego uniknąć, albo zwracasz wskaźnik do pamięci, która znajduje się na stercie, np. lineBuffer, a użytkownik powinien być odpowiedzialny za wywołanie free (), kiedy to zrobi. Alternatywnie możesz poprosić użytkownika o podanie jako argumentu adresu pamięci, na którym chcesz zapisać zawartość wiersza.


Istnieje różnica między zachowaniem niezgodnym z prawem a niezdefiniowanym ^^.
Phong

0

Chcę kod z poziomu 0, więc zrobiłem to, aby odczytać treść słowa słownika wiersz po wierszu.

char temp_str [20]; // możesz zmienić rozmiar bufora zgodnie ze swoimi wymaganiami I długość pojedynczej linii w pliku.

Uwaga : Zainicjowałem bufor znakiem Null za każdym razem, gdy czytam wiersz. Ta funkcja może być zautomatyzowana, ale ponieważ potrzebuję proof of concept i chcę zaprojektować 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;
}

twój program działałby, gdyby nawiasy int main() {
kwadratowe

Nawiasem mówiąc, nie musisz podawać wszystkich 20 '\ 0'. Możesz po prostu napisać: codechar temp_str [20] = {'\ 0'}; code c automatycznie wypełni każde pole zerowym terminatorem, ponieważ sposób działania deklaracji tablicowych polega na tym, że jeśli tablica zostanie zainicjowana z mniejszą liczbą elementów, które zawiera tablica, ostatni element wypełni pozostałe elementy.
alaniane

Wierzę, że char temp_str[20] = {0}również wypełnia całą tablicę znaków terminatorami zerowymi.
Thu Yein Tun

0

Moje narzędzie od zera:

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

Dlaczego używasz sterty (malloc) zamiast stosu? Wydaje się, że można zastosować prostsze rozwiązanie oparte na stosie fgets.
theicfire,

0

Zapewnij przenośną i ogólną getdelimfunkcję, test przeszedł przez 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;
}

Po co to robić fgets?
theicfire

czy fgets mogą dostosowywać ograniczniki linii lub dostosowywać co zrobić z bieżącymi liniami?
南山 竹

getdelimpozwala na dostosowanie ograniczników. Zauważam też, że nie mam limitu długości linii - w tym przypadku możesz użyć stosu getline. (Oba opisane tutaj: man7.org/linux/man-pages/man3/getline.3.html )
theicfire

mówisz tylko o Linuksie, pytanie brzmi, jak czytać wiersz w C, prawda?
南山 竹

Działa to dla każdej standardowej implementacji c ( getdelimi getlinezostały znormalizowane w POSIX.1-2008, ktoś inny wspomina na tej stronie). fgetsjest również standardem c, a nie specyficznym dla systemu Linux
theicfire
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.