C lee el archivo línea por línea

184

Escribí esta función para leer una línea de un archivo:

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 función lee el archivo correctamente, y usando printf veo que la cadena constLine también se leyó correctamente.

Sin embargo, si uso la función, por ejemplo, así:

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

printf produce galimatías. ¿Por qué?

lron
fuente
Usar en fgetslugar de fgetc. Estás leyendo carácter por carácter en lugar de línea por línea.
Shiv
3
Tenga en cuenta que getline()es parte de POSIX 2008. Puede haber plataformas similares a POSIX sin él, especialmente si no son compatibles con el resto de POSIX 2008, pero dentro del mundo de los sistemas POSIX, getline()es bastante portátil en estos días.
Jonathan Leffler

Respuestas:

305

Si su tarea no es inventar la función de lectura línea por línea, sino solo leer el archivo línea por línea, puede usar un fragmento de código típico que involucre la getline()función (consulte la página del manual aquí ):

#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);
}
mbaitoff
fuente
83
Eso no es portátil.
JeremyP
16
Más precisamente, esto getlinees específico de GNU libc, es decir, de Linux. Sin embargo, si la intención es tener una función de lectura de línea (en lugar de aprender C), hay varias funciones de lectura de línea de dominio público disponibles en la web.
Gilles 'SO- deja de ser malvado'
11
¿Por qué debería hacer eso? Lea el manual, el búfer se reasigna en cada llamada, luego debe liberarse al final.
mbaitoff
29
El if(line)cheque es superfluo. Llamar free(NULL)es esencialmente un no-op.
Aroth
50
Para aquellos que dijeron que este getline es específico de GNU libc, "getline () y getdelim () eran originalmente extensiones de GNU. Estaban estandarizados en 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);
Robar
fuente
Para mí, esto resulta en sobrescribir cada línea con la siguiente. Vea esta pregunta basada en la respuesta anterior.
Cezar Cobuz
55
¿Por qué el elenco (FILE*) fp? ¿No fpes ya una FILE *y también fopen()devuelve una FILE *?
Contador م
1
Si está de acuerdo con que las líneas se limiten a una cierta longitud, esta es la mejor respuesta. De lo contrario, usar getlinees una buena alternativa. Estoy de acuerdo en que el FILE *reparto es innecesario.
theicfire
Eliminé el molde innecesario, agregué una variable para la longitud del búfer y cambié fpa filePointerpara mayor claridad.
Rob
21

En su readLinefunción, devuelve un puntero a la linematriz (en sentido estricto, un puntero a su primer carácter, pero la diferencia es irrelevante aquí). Como es una variable automática (es decir, está "en la pila"), la memoria se recupera cuando la función regresa. Ves galimatías porque printfha puesto sus propias cosas en la pila.

Debe devolver un búfer asignado dinámicamente desde la función. Ya tienes uno, es lineBuffer; todo lo que tiene que hacer es truncarlo a la longitud deseada.

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

AGREGADO (respuesta a la pregunta de seguimiento en el comentario): readLinedevuelve un puntero a los caracteres que forman la línea. Este puntero es lo que necesita para trabajar con el contenido de la línea. También es a lo que debe pasar freecuando haya terminado de usar la memoria tomada por estos personajes. Así es como puede usar la readLinefunción:

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). */
Gilles 'SO- deja de ser malvado'
fuente
@Iron: agregué algo a mi respuesta, pero no estoy seguro de cuál es su dificultad, por lo que puede estar fuera de lugar.
Gilles 'SO- deja de ser malvado'
@Iron: la respuesta es que no lo liberas. Usted documenta (en la documentación de la API) que la persona que llama debe liberar el búfer devuelto. Luego, las personas que usan su función readLine (¡con suerte!) Escribirán un código similar al fragmento que Gilles ha agregado a su respuesta.
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
RevoLab
fuente
1
Hay algunos problemas con este código: fopen_shace que el código no sea portátil. printfbuscará especificadores de formato y no imprimirá signos de porcentaje y los siguientes caracteres como son . Los bytes nulos harán que todos los caracteres del resto de la línea desaparezcan. (¡No me digas que no pueden ocurrir bytes nulos!)
hagello
Y por cierto, no resuelves el problema. El OP describe que el valor de retorno de su función desaparece. No te veo abordando este problema.
hagello
@Hartley Sé que este es un comentario anterior, pero estoy agregando esto para que alguien no lea su comentario e intente liberar (línea) en el bucle. La memoria para la línea solo se asigna una vez antes de que comience el ciclo, por lo que solo debe estar libre una vez después de que finalice el ciclo. Si intenta liberar la línea dentro del bucle, obtendrá resultados inesperados. Dependiendo de cómo free () trata el puntero. Si solo desasigna la memoria y deja el puntero apuntando a la ubicación anterior, el código puede funcionar. Si asigna otro valor al puntero, sobrescribirá una sección diferente de la memoria.
alaniane
2
¡printf (línea) está mal! No hagas esto. Esto abre su código a una vulnerabilidad de formato de cadena donde puede leer / escribir libremente directamente en la memoria a través de las cosas que se imprimen. Si tuviera que poner% n /% p en el archivo y apuntar el puntero a una dirección en la memoria (en la cadena del archivo) que controlaba, podría ejecutar ese código.
oxagast
10

readLine() devuelve el puntero a la variable local, lo que provoca un comportamiento indefinido.

Para moverte puedes:

  1. Crear variable en la función de llamante y pasar su dirección a readLine()
  2. Asignar memoria para lineusar malloc(), en este casoline será persistente
  3. Utilice la variable global, aunque generalmente es una mala práctica.
qrdl
fuente
7

Se usa fgets()para leer una línea desde un identificador de archivo.

Raku Escape
fuente
4

Algunas cosas están mal con el ejemplo:

  • olvidó agregar \ n a sus printfs. También los mensajes de error deben ir a stderr, es decirfprintf(stderr, ....
  • (No es un biggy pero) considere usar en fgetc()lugar de getc(). getc()es una macrofgetc() es una función adecuada
  • getc()devuelve un intso chdebe declararse como un int. Esto es importante ya que la comparación con EOFse manejará correctamente. Algunos juegos de caracteres de 8 bits se usan 0xFFcomo caracteres válidos (ISO-LATIN-1 sería un ejemplo) y EOFque es -1, se 0xFFasignará a a char.
  • Hay un potencial desbordamiento del búfer en la línea

    lineBuffer[count] = '\0';

    Si la línea tiene exactamente 128 caracteres, countes 128 en el punto que se ejecuta.

  • Como otros han señalado, linees una matriz declarada localmente. No puede devolverle un puntero.

  • strncpy(count + 1)copiará a la mayoría de los count + 1caracteres pero terminará si golpea'\0' Debido configura lineBuffer[count]a '\0'que sabe que nunca va a llegar a count + 1. Sin embargo, si lo hiciera, no pondría una terminación '\0', por lo que debe hacerlo. A menudo ves algo como lo siguiente:

    char buffer [BUFFER_SIZE];
    strncpy(buffer, sourceString, BUFFER_SIZE - 1);
    buffer[BUFFER_SIZE - 1] = '\0';
  • si tiene malloc()una línea para devolver (en lugar de su charmatriz local ), su tipo de retorno debería ser char*: suelte elconst .

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

¿Qué hay de este?

Taner Mansur
fuente
2

Aquí están mis varias horas ... Leyendo todo el archivo línea por línea.

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;
}
Sam
fuente
1
¿Por qué estás usando en fgetclugar de 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);
}

tenga en cuenta que la variable 'línea' se declara en la función de llamada y luego se pasa, por lo que su readLinefunción llena el búfer predefinido y simplemente lo devuelve. Esta es la forma en que funcionan la mayoría de las bibliotecas de C.

Hay otras formas, de las cuales soy consciente:

  • definiendo el char line[]como estático (static char line[MAX_LINE_LENGTH] -> mantendrá su valor DESPUÉS de regresar de la función). -> mal, la función no es reentrante y puede producirse una condición de carrera -> si lo llama dos veces desde dos hilos, sobrescribirá sus resultados
  • malloc()ing la línea char [], y liberándola en funciones de llamada -> demasiados correos caros malloc, y delegando la responsabilidad de liberar el buffer a otra función (la solución más elegante es llamar mallocy freeen cualquier buffer en la misma función)

por cierto, la conversión 'explícita' de char*a const char*es redundante.

por cierto, no hay necesidad de malloc()lineBuffer, solo defínalo, por char lineBuffer[128]lo que no es necesario liberarlo

por cierto, no use 'matrices de pila de tamaño dinámico' (definiendo la matriz como char arrayName[some_nonconstant_variable]), si no sabe exactamente qué está haciendo, solo funciona en C99.

nada
fuente
1
tenga en cuenta que la variable 'línea' se declara en la función de llamada y luego se pasa, entonces probablemente debería haber eliminado la declaración de línea local en la función. Además, debe decirle a la función cuánto tiempo dura el búfer y pensar en una estrategia para manejar líneas que son demasiado largas para el búfer que pasa.
JeremyP
1

Debe usar las funciones ANSI para leer una línea, por ejemplo. Fgets. Después de llamar, necesita free () en el contexto de la llamada, por ejemplo:

...
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;
}
usuario411313
fuente
1

Implemente un método para leer y obtener contenido de un archivo (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);
}

Espero que esto ayude. ¡Feliz codificación!

Nhat Dinh
fuente
0

Comete el error de devolver un puntero a una variable automática. La línea variable se asigna en la pila y solo vive mientras viva la función. No está permitido devolverle un puntero, porque tan pronto como regrese, la memoria se dará en otro lugar.

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

Para evitar esto, puede devolver un puntero a la memoria que reside en el montón, por ejemplo. lineBuffer y debería ser responsabilidad del usuario llamar a free () cuando haya terminado con él. Alternativamente, puede pedirle al usuario que le pase como argumento una dirección de memoria en la que escribir el contenido de la línea.

Lefteris E
fuente
Hay una diferencia entre el comportamiento ilegal y el indefinido ^^.
Phong
0

Quiero un código de la base 0, así que hice esto para leer el contenido de la palabra del diccionario línea por línea.

char temp_str [20]; // puede cambiar el tamaño del búfer de acuerdo con sus requisitos y la longitud de una sola línea en un archivo.

Tenga en cuenta que he inicializado el búfer con carácter nulo cada vez que leo la línea. Esta función se puede automatizar pero ya que necesito una prueba de concepto y quiero diseñar un programa 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;
}
Mohit Dabas
fuente
su programa funcionaría si sus paréntesis estuvieran en los lugares correctos;) por ejemploint main() {
dylnmc
Por cierto, no necesita especificar todos los 20 '\ 0'. Simplemente puede escribir: codechar temp_str [20] = {'\ 0'}; code c llenará automáticamente cada ranura con un terminador nulo, ya que la forma en que funcionan las declaraciones de matriz es que si una matriz se inicializa con menos elementos que contiene, el último elemento completará los elementos restantes.
alaniane
Creo que char temp_str[20] = {0}también llena toda la matriz de caracteres con terminadores nulos.
Jue Yein Tun
0

Mi implemento desde cero:

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);
tjeubaoit
fuente
¿Por qué estás usando el montón (malloc) en lugar de la pila? Parece que hay una solución más simple basada en la pila fgetsque podría usarse.
theicfire
0

Proporcione una función portátil y genérica getdelim, prueba aprobada a través de 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;
}
南山 竹
fuente
¿Por qué hacer esto cuando fgetsexiste?
theicfire
Qué fgets puede personalizar los delimitadores de línea o personalizar qué hacer con las líneas actuales?
南山 竹
getdelimpermite delimitadores personalizados. También noto que no tengo un límite de longitud de línea, en este caso puedes usar la pila con getline. (Ambos descritos aquí: man7.org/linux/man-pages/man3/getline.3.html )
theicfire
¿Hablas solo de Linux? La pregunta es sobre cómo leer la línea en C, ¿verdad?
南山 竹
Esto funciona para cualquier implementación estándar de c ( getdelimy getlinese estandarizó en POSIX.1-2008, alguien más lo menciona en esta página). fgetstambién es estándar c, y no es específico de Linux
theicfire