C ler arquivo linha por linha


184

Eu escrevi esta função para ler uma linha de um arquivo:

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

A função lê o arquivo corretamente e, usando printf, vejo que a string constLine também foi lida corretamente.

No entanto, se eu usar a função, por exemplo:

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

printf produz sem sentido. Por quê?


Use em fgetsvez de fgetc. Você está lendo caractere por caractere em vez de linha por linha.
Shiv

3
Observe que isso getline()faz parte do POSIX 2008. Pode haver plataformas semelhantes ao POSIX sem ele, especialmente se elas não suportam o restante do POSIX 2008, mas dentro do mundo dos sistemas POSIX, getline()é bastante portátil atualmente.
Jonathan Leffler

Respostas:


305

Se sua tarefa não é inventar a função de leitura linha a linha, mas apenas para ler o arquivo linha por linha, você pode usar um trecho de código típico envolvendo a getline()função (consulte a página do manual aqui ):

#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
Isso não é portátil.
precisa saber é o seguinte

16
Mais precisamente, isso getlineé específico ao GNU libc, ou seja, ao Linux. No entanto, se a intenção é ter uma função de leitura de linha (em vez de aprender C), existem várias funções de leitura de linha de domínio público disponíveis na Web.
Gilles 'SO- stop be evil'

11
Por que eu deveria fazer aquilo? Leia o manual, o buffer é realocado a cada chamada e deve ser liberado no final.
mbaitoff

29
O if(line)cheque é supérfluo. Ligar free(NULL)é essencialmente um não-op.
Aroth

50
Para aqueles que disseram que este getline é específico ao GNU libc, "Ambos getline () e getdelim () eram originalmente extensões do GNU. Eles foram padronizados no POSIX.1-2008."
precisa saber é o seguinte

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

Para mim, isso resulta na substituição de cada linha pela seguinte. Veja esta pergunta com base na resposta acima.
Cezar Cobuz 8/01/19

5
Por que o elenco (FILE*) fp? Já não fpé um FILE *e também fopen()retorna um FILE *?
Contador م

1
Se você concorda com as linhas limitadas a um determinado comprimento, esta é a melhor resposta. Caso contrário, usar getlineé uma boa alternativa. Concordo que o FILE *elenco é desnecessário.
Theicfire 17/10/19

I removido do molde un-necessário, adicionou-se uma variável para o comprimento do buffer e mudou fppara filePointeruma maior clareza.
Rob

21

Em sua readLinefunção, você retorna um ponteiro para a linematriz (Estritamente falando, um ponteiro para seu primeiro caractere, mas a diferença é irrelevante aqui). Como é uma variável automática (ou seja, está “na pilha”), a memória é recuperada quando a função retorna. Você vê bobagens porque printfcolocou suas próprias coisas na pilha.

Você precisa retornar um buffer alocado dinamicamente da função Você já tem um, é lineBuffer; tudo o que você precisa fazer é truncá-lo no comprimento desejado.

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

ADICIONADO (resposta à pergunta de acompanhamento no comentário): readLineretorna um ponteiro para os caracteres que compõem a linha. Esse ponteiro é o que você precisa para trabalhar com o conteúdo da linha. É também para isso que você deve passar freequando terminar de usar a memória usada por esses caracteres. Veja como você pode usar a readLinefunção:

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: Eu adicionei algo à minha resposta, mas não tenho certeza qual é a sua dificuldade, por isso pode estar errado.
Gilles 'SO- stop be evil'

@ Iron: a resposta é que você não a liberta. Você documenta (na documentação da API) o fato de que o buffer retornado é malloc'd e ansd precisa ser liberado pelo chamador. Em seguida, as pessoas que usam sua função readLine (espero!) Escreverão um código semelhante ao trecho que Gilles adicionou à sua resposta.
precisa saber é o seguinte

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
Existem alguns problemas com este código: fopen_storna o código não portável. printfprocurará especificadores de formato e não imprimirá sinais de porcentagem e os seguintes caracteres como estão . Bytes nulos farão desaparecer todos os caracteres no restante da linha. (Não me diga nulo bytes não pode acontecer!)
hagello

E, a propósito, você não resolve o problema. O OP descreve que o valor de retorno de sua função desaparece. Não vejo você abordando esse problema.
hagello

@ Hartley Eu sei que este é um comentário mais antigo, mas estou adicionando isso para que alguém não leia seu comentário e tente liberar (linha) no loop. A memória da linha é alocada apenas uma vez antes do início do loop, portanto, deve ficar livre apenas uma vez após o término do loop. Se você tentar liberar a linha dentro do loop, obterá resultados inesperados. Dependendo de como free () trata o ponteiro. Se apenas desalocar a memória e deixar o ponteiro apontando para o local antigo, o código poderá funcionar. Se ele atribuir outro valor ao ponteiro, você substituirá uma seção diferente da memória.
Alaniane 20/0118

2
printf (linha) está errado! Não faça isso. Isso abre seu código para uma vulnerabilidade de formato de sequência, na qual você pode ler / gravar livremente diretamente na memória através do material que está sendo impresso. Se eu colocar% n /% p no arquivo e apontar o ponteiro para um endereço na memória (na sequência do arquivo) que eu controlei, eu poderia executar esse código.
Oxagast

10

readLine() retorna o ponteiro para a variável local, o que causa um comportamento indefinido.

Para se locomover, você pode:

  1. Crie uma variável na função de chamada e passe seu endereço para readLine()
  2. Alocar memória para lineuso malloc()- neste caso line, será persistente
  3. Use variável global, embora geralmente seja uma má prática


4

Algumas coisas estão erradas com o exemplo:

  • você esqueceu de adicionar \ n aos seus printfs. As mensagens de erro também devem ir para stderrfprintf(stderr, ....
  • (não é nada demais) considere usar fgetc()e não getc(). getc()é uma macro, fgetc()é uma função adequada
  • getc()retorna um intso chdeve ser declarado como um int. Isso é importante, pois a comparação com EOFserá tratada corretamente. Alguns conjuntos de caracteres de 8 bits usam 0xFFcomo um caractere válido (ISO-LATIN-1 seria um exemplo) e EOFque é -1, será 0xFFse atribuído a a char.
  • Existe um potencial estouro de buffer na linha

    lineBuffer[count] = '\0';

    Se a linha tiver exatamente 128 caracteres, countserá 128 no ponto que é executado.

  • Como outros já apontaram, lineé uma matriz declarada localmente. Você não pode retornar um ponteiro para ele.

  • strncpy(count + 1)irá copiar a maioria dos count + 1personagens, mas terminará se bate '\0' Porque você definir lineBuffer[count]para '\0'que você sabe que nunca vai conseguir count + 1. No entanto, se o fizesse, não colocaria uma terminação '\0', então você precisa fazê-lo. Você costuma ver algo como o seguinte:

    char buffer [BUFFER_SIZE];
    strncpy(buffer, sourceString, BUFFER_SIZE - 1);
    buffer[BUFFER_SIZE - 1] = '\0';
  • se você tem malloc()uma linha para retornar (no lugar de sua charmatriz local ), seu tipo de retorno deve ser char*- solte o 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);
    }
}

que tal este?


2

Aqui estão minhas várias horas ... Lendo todo o arquivo linha por linha.

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
Por que você está usando em fgetcvez de fgets?
Theicfire 17/10/19

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

note que a variável 'line' é declarada na função de chamada e depois passada, então sua readLinefunção preenche o buffer predefinido e apenas o retorna. É assim que a maioria das bibliotecas C funciona.

Existem outras maneiras pelas quais estou ciente:

  • definindo char line[]como estático ( static char line[MAX_LINE_LENGTH] -> manterá seu valor APÓS retornar da função). -> ruim, a função não é reentrada, e a condição de corrida pode ocorrer -> se você chamá-lo duas vezes a partir de dois threads, substituirá seus resultados
  • malloc()adicionando a linha char [] e liberando-a nas funções de chamada -> muitos mallocs caros , e delegando a responsabilidade de liberar o buffer para outra função (a solução mais elegante é chamar malloce freeem qualquer buffer na mesma função)

btw, a conversão "explícita" de char*para const char*é redundante.

btw2, não há necessidade malloc()do lineBuffer, apenas defina-o char lineBuffer[128], para que você não precise liberá -lo

btw3 não usa 'matrizes de pilha de tamanho dinâmico' (definindo a matriz como char arrayName[some_nonconstant_variable]), se você não sabe exatamente o que está fazendo, ele funciona apenas no C99.


1
note que a variável 'linha' é declarada na função de chamada e depois passada - você provavelmente deveria ter excluído a declaração de linha local na função. Além disso, você precisa dizer a função de quanto tempo o tampão é que você está passando e pensar em uma estratégia para lidar com linhas que são demasiado longos para o buffer de você passar no.
JeremyP

1

Você deve usar as funções ANSI para ler uma linha, por exemplo. objetos. Depois de ligar, você precisa de free () no contexto de chamada, por exemplo:

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

Implementar método para ler e obter conteúdo de um arquivo (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 esta ajuda. Feliz codificação!


0

Você comete o erro de retornar um ponteiro para uma variável automática. A linha variável é alocada na pilha e dura apenas enquanto a função durar. Você não tem permissão para retornar um ponteiro para ele, porque assim que ele retornar, a memória será entregue em outro lugar.

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

Para evitar isso, você retorna um ponteiro para a memória que reside na pilha, por exemplo. lineBuffer e deve ser responsabilidade do usuário ligar para free () quando ele terminar. Como alternativa, você pode pedir ao usuário para passar como argumento um endereço de memória no qual escrever o conteúdo da linha.


Há uma diferença entre comportamento ilegal e indefinido ^^.
Phong

0

Eu quero um código do chão 0, então eu fiz isso para ler o conteúdo da palavra do dicionário linha por linha.

char temp_str [20]; // você pode alterar o tamanho do buffer de acordo com seus requisitos E o comprimento de uma única linha em um arquivo.

Nota : Inicializei o buffer Com o caractere Nulo toda vez que leio a linha.

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

seu programa iria funcionar se seus suportes estavam nos lugares certos;), por exemploint main() {
dylnmc

Aliás, você não precisa especificar todos os 20 '\ 0'. Você pode simplesmente escrever: codechar temp_str [20] = {'\ 0'}; code c preencherá automaticamente cada slot com um terminador nulo, pois a maneira como as declarações da matriz funcionam é que, se uma matriz for inicializada com menos elementos que a matriz contém, o último elemento preencherá os elementos restantes.
alaniane

Eu acredito que char temp_str[20] = {0}também preenche toda a matriz de caracteres com terminadores nulos.
Qui yein Tun

0

Meu implemento do 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);

Por que você está usando o heap (malloc) em vez da pilha? Parece que há uma solução mais simples baseada em pilha fgetsque poderia ser usada.
theicfire 17/10/19

0

Forneça uma getdelimfunção portátil e genérica , teste passado via 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;
}

Por que fazer isso quando fgetsexiste?
theicfire 17/10/19

o fgets pode personalizar delimitadores de linha ou o que fazer com as linhas atuais?
南山竹

getdelimpermite delimitadores personalizados. Também notei que não há limite de comprimento de linha - nesse caso, você pode usar a pilha com getline. (Ambos descritos aqui: man7.org/linux/man-pages/man3/getline.3.html )
theicfire

você fala apenas sobre Linux, a questão é sobre como ler linhas em C, certo?
南山竹

Isso funciona para qualquer implementação padrão c ( getdelime getlinefoi padronizada no POSIX.1-2008, alguém menciona nesta página). fgetstambém é padrão c, e não específico do Linux
theicfire 17/10/19
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.