C legge il file riga per riga


184

Ho scritto questa funzione per leggere una riga da un 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;
}

La funzione legge correttamente il file e usando printf vedo che anche la stringa constLine è stata letta correttamente.

Tuttavia, se uso la funzione ad esempio in questo modo:

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

printf genera in modo incomprensibile. Perché?


Usa fgetsinvece di fgetc. Stai leggendo carattere per carattere anziché riga per riga.
Shiv

3
Si noti che getline()fa parte di POSIX 2008. Potrebbero esserci piattaforme simili a POSIX senza di essa, specialmente se non supportano il resto di POSIX 2008, ma nel mondo dei sistemi POSIX, getline()è abbastanza portatile in questi giorni.
Jonathan Leffler

Risposte:


305

Se il tuo compito non è inventare la funzione di lettura riga per riga, ma solo leggere il file riga per riga, puoi utilizzare un tipico frammento di codice che coinvolge la getline()funzione (vedi la pagina del manuale qui ):

#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
Non è portatile.
JeremyP,

16
Più precisamente, questo getlineè specifico per GNU libc, cioè per Linux. Tuttavia, se l'intenzione è quella di avere una funzione di lettura della linea (al contrario dell'apprendimento C), ci sono diverse funzioni di lettura della linea di dominio pubblico disponibili sul web.
Gilles 'SO- smetti di essere malvagio' il

11
Perché dovrei farlo? Leggere il manuale, il buffer viene riallocato ad ogni chiamata, quindi dovrebbe essere liberato alla fine.
mbaitoff,

29
Il if(line)controllo è superfluo. La chiamata free(NULL)è essenzialmente no-op.
aroth,

50
Per coloro che hanno affermato che questa getline è specifica per GNU libc, "Sia getline () che getdelim () erano originariamente estensioni GNU. Sono state standardizzate in 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);

Per me questo comporta la sovrascrittura di ogni riga con la successiva. Vedi questa domanda in base alla risposta sopra.
Cezar Cobuz,

5
Perché il cast (FILE*) fp? Non fpè già un FILE *e fopen()restituisce anche un FILE *?
Ragioniere م

1
Se stai bene con le linee limitate a una certa lunghezza, questa è la risposta migliore. Altrimenti l'uso getlineè una buona alternativa. Sono d'accordo che il FILE *cast non sia necessario.
theicfire

Ho rimosso il cast non necessario, aggiunto una variabile per la lunghezza del buffer e modificato fpin filePointerper maggiore chiarezza.
Rob

21

Nella tua readLinefunzione, restituisci un puntatore lineall'array (A rigor di termini, un puntatore al suo primo carattere, ma la differenza non è rilevante qui). Poiché si tratta di una variabile automatica (ovvero, "in pila"), la memoria viene recuperata quando la funzione ritorna. Vedi senza senso perché printfha messo le sue cose in pila.

È necessario restituire un buffer allocato dinamicamente dalla funzione. Ne hai già uno, è lineBuffer; tutto quello che devi fare è troncarlo alla lunghezza desiderata.

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

AGGIUNTO (risposta alla domanda di follow-up nel commento): readLinerestituisce un puntatore ai caratteri che compongono la linea. Questo puntatore è ciò di cui hai bisogno per lavorare con i contenuti della linea. È anche ciò a cui devi passare freequando hai finito di usare la memoria di questi personaggi. Ecco come è possibile utilizzare la readLinefunzione:

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: ho aggiunto qualcosa alla mia risposta, ma non sono sicuro di quale sia la tua difficoltà, quindi potrebbe essere fuori dal comune.
Gilles 'SO- smetti di essere malvagio' il

@Iron: la risposta è che non lo si libera. Si documenta (nella documentazione dell'API) il fatto che il buffer restituito sia mallocato e che deve essere liberato dal chiamante. Quindi le persone che usano la tua funzione readLine (si spera!) Scriveranno un codice simile allo snippet che Gilles ha aggiunto alla sua risposta.
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
Ci sono alcuni problemi con questo codice: fopen_srende il codice non portabile. printfcercherà gli identificatori di formato e non stamperà i segni di percentuale e i seguenti caratteri così come sono . I byte null faranno scomparire tutti i caratteri nel resto della riga. (Non dirmi che byte nulli non possono accadere!)
hagello,

E a proposito, non risolvi il problema. L'OP descrive che il valore di ritorno della sua funzione scompare. Non ti vedo affrontare questo problema.
hagello,

@Hartley So che questo è un commento più vecchio, ma lo sto aggiungendo in modo che qualcuno non legga il suo commento e provi a liberare (linea) nel ciclo. La memoria per la linea viene allocata una sola volta prima dell'inizio del ciclo, quindi dovrebbe essere libera solo una volta dopo la fine del ciclo. Se provi a liberare la linea all'interno del ciclo, otterrai risultati imprevisti. A seconda di come free () tratta il puntatore. Se si sposta solo la memoria e lascia il puntatore puntato nella vecchia posizione, il codice potrebbe funzionare. Se assegna un altro valore al puntatore, sovrascriverete una diversa sezione della memoria.
alaniane,

2
printf (linea) è sbagliato! Non farlo. Questo apre il tuo codice a una vulnerabilità in formato stringa in cui puoi liberamente leggere / scrivere direttamente in memoria tramite gli oggetti in stampa. Se dovessi inserire% n /% p nel file e puntare il puntatore su un indirizzo in memoria (nella stringa del file) che ho controllato, potrei eseguire quel codice.
Oxagast,

10

readLine() restituisce il puntatore alla variabile locale, che causa un comportamento indefinito.

Per aggirare puoi:

  1. Crea una variabile nella funzione chiamante e passa il suo indirizzo a readLine()
  2. Alloca memoria per l' lineutilizzo malloc()- in questo casoline sarà persistente
  3. Usa la variabile globale, sebbene sia generalmente una cattiva pratica


4

Alcune cose che non vanno nell'esempio:

  • hai dimenticato di aggiungere \ n ai tuoi printfs. Anche i messaggi di errore dovrebbero andare a stderr iefprintf(stderr, ....
  • (non un grosso ma) considera di usare fgetc()piuttosto che getc(). getc()è una macro, fgetc()è una funzione adeguata
  • getc()ritorna un intcosì chdovrebbe essere dichiarato come un int. Questo è importante poiché il confronto con EOFverrà gestito correttamente. Alcuni set di caratteri a 8 bit utilizzano 0xFFcome carattere valido (ISO-LATIN-1 sarebbe un esempio) e EOFche è -1, verrà 0xFFassegnato se assegnato a char.
  • C'è un potenziale overflow del buffer sulla linea

    lineBuffer[count] = '\0';

    Se la riga è lunga esattamente 128 caratteri, countè 128 nel punto in cui viene eseguita.

  • Come altri hanno sottolineato, lineè un array dichiarato localmente. Non è possibile restituire un puntatore ad esso.

  • strncpy(count + 1)copierà al massimo i count + 1personaggi ma terminerà se colpisce '\0' Perché hai impostato lineBuffer[count]su '\0'sai che non ci riuscirà mai count + 1. Tuttavia, se lo facesse, non si chiuderebbe '\0', quindi è necessario farlo. Spesso vedi qualcosa di simile al seguente:

    char buffer [BUFFER_SIZE];
    strncpy(buffer, sourceString, BUFFER_SIZE - 1);
    buffer[BUFFER_SIZE - 1] = '\0';
  • se si malloc()desidera restituire una riga (al posto chardell'array locale ), il tipo di ritorno deve essere char*: rilasciare il 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);
    }
}

che dire di questo?


2

Ecco le mie diverse ore ... Leggere l'intero file riga per riga.

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
Perché stai usando fgetcinvece di 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);
}

nota che la variabile 'line' viene dichiarata nella funzione di chiamata e quindi passata, quindi la tua readLine funzione riempie il buffer predefinito e la restituisce. Questo è il modo in cui funziona la maggior parte delle librerie C.

Esistono altri modi, di cui sono a conoscenza:

  • definendo char line[]come statico ( static char line[MAX_LINE_LENGTH] -> manterrà il suo valore DOPO tornare dalla funzione). -> non valido, la funzione non è rientrante e possono verificarsi condizioni di competizione -> se la chiami due volte da due thread, sovrascriverà i risultati
  • malloc()ing la linea char [] e liberandola nelle funzioni di chiamata -> troppi messaggi costosi malloce, delegando la responsabilità di liberare il buffer ad un'altra funzione (la soluzione più elegante è quella di chiamare malloce freesu tutti i buffer nella stessa funzione)

tra l'altro, il cast "esplicito" da char*a const char*è ridondante.

btw2, non è necessario malloc()il lineBuffer, basta definirlo char lineBuffer[128], quindi non è necessario liberarlo

btw3 non usa 'array di stack di dimensioni dinamiche' (definendo l'array come char arrayName[some_nonconstant_variable]), se non sai esattamente cosa stai facendo, funziona solo in C99.


1
si noti che la variabile 'line' viene dichiarata nella funzione di chiamata e quindi passata, quindi probabilmente si dovrebbe aver eliminato la dichiarazione di riga locale nella funzione. Inoltre, devi dire alla funzione quanto tempo passa il buffer che stai passando e pensare a una strategia per gestire linee troppo lunghe per il buffer in cui passi.
JeremyP

1

È necessario utilizzare le funzioni ANSI per leggere una riga, ad es. fgets. Dopo aver chiamato hai bisogno di free () nel contesto di chiamata, ad esempio:

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

Implementare il metodo per leggere e ottenere il contenuto da un 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);
}

Spero che questo aiuto. Buona programmazione!


0

Si commette l'errore di restituire un puntatore a una variabile automatica. La linea variabile è allocata nello stack e vive solo finché la funzione è attiva. Non è consentito restituire un puntatore ad esso, poiché non appena restituisce la memoria verrà data altrove.

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

Per evitare ciò, si restituisce un puntatore alla memoria che risiede nell'heap, ad es. lineBuffer e dovrebbe essere responsabilità dell'utente chiamare free () quando ha finito con esso. In alternativa puoi chiedere all'utente di passare come argomento un indirizzo di memoria su cui scrivere il contenuto della riga.


C'è una differenza tra comportamento illegale e non definito ^^.
Phong,

0

Voglio un codice da terra 0, quindi ho fatto questo per leggere il contenuto del dizionario parola per riga.

char temp_str [20]; // è possibile modificare la dimensione del buffer in base alle proprie esigenze e alla lunghezza di una singola riga in un file.

Nota Ho inizializzato il buffer con il carattere Null ogni volta che leggo la riga. Questa funzione può essere automatizzata ma poiché ho bisogno di una prova di concetto e voglio progettare un programma 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;
}

il tuo programma funzionerebbe se le tue parentesi fossero nei posti giusti;) ad es.int main() {
dylnmc

Per inciso, non è necessario specificare tutti i 20 '\ 0'. Puoi semplicemente scrivere: codechar temp_str [20] = {'\ 0'}; code c riempirà automaticamente ogni slot con un terminatore nullo poiché il modo in cui funzionano le dichiarazioni di array è che se un array viene inizializzato con meno elementi che contiene l'array, l'ultimo elemento riempirà gli elementi rimanenti.
alaniane,

Credo che char temp_str[20] = {0}riempia anche l'intero array di caratteri con terminatori null.
Gio Yein Tun,

0

La mia macchina da zero:

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

Perché stai usando l'heap (malloc) invece dello stack? Sembra che ci sia una soluzione basata su stack più semplice fgetsche potrebbe essere utilizzata.
theicfire

0

Fornire una funzione portatile e generica getdelim, test superato tramite 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;
}

Perché farlo quando fgetsesiste?
theicfire,

i budget possono personalizzare i delimitatori di linea o personalizzare cosa fare delle linee attuali?
南山 竹

getdelimconsente delimitatori personalizzati. Inoltre noto che non ha un limite di lunghezza della linea - in questo caso puoi usare lo stack con getline. (Entrambi descritti qui: man7.org/linux/man-pages/man3/getline.3.html )
theicfire

parli solo di Linux, la domanda è su come leggere la riga in C, giusto?
南山 竹

Funziona con qualsiasi implementazione standard c ( getdelimed è getlinestato standardizzato in POSIX.1-2008, qualcun altro menziona questa pagina). fgetsè anche standard c, e non specifico per Linux
theicfire
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.