Última linha duplicada com feof+fgets

Introdução

A leitura de linhas de arquivos é muito comum em programação e o modo mais usual de se fazer isso é usando a função feof como condição de loop e a fgets para a leitura das linhas. Porém, se essas duas funções não forem usadas corretamente, a última linha poderá ser processada duas vezes.

feof+fgets

O modo incorreto de se usar essas duas funções é assim:

[feoferro.c]
#include <stdio.h>

int main() {
    FILE *arq ;
    char buf[BUFSIZ] ;

    arq = fopen("/etc/passwd", "r") ;

    while(!feof(arq)) {
        fgets(buf, BUFSIZ, arq) ;
        printf("%s", buf) ;
    }

    fclose(arq) ;
    return 0 ;
}

O arquivo “/etc/passwd” será aberto para leitura e suas linhas serão armazenadas no buffer buf e, posteriormente, mostradas na tela. Porém, como podemos ver na execução abaixo, a última linha foi processada duas vezes.

$ gcc -o feoferro feoferro.c
$ ./feoferro | tail
nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
backuppc:x:992:988::/var/lib/BackupPC:/sbin/nologin
postgres:x:26:26:PostgreSQL Server:/var/lib/pgsql:/bin/bash
squid:x:23:23::/var/spool/squid:/sbin/nologin
chrony:x:991:987::/var/lib/chrony:/sbin/nologin
distcache:x:94:94:Distcache:/:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin
daemonio:x:1000:1000:Marcos Paulo:/home/daemonio:/bin/bash
daemonio:x:1000:1000:Marcos Paulo:/home/daemonio:/bin/bash

O erro

A função feof retorna verdadeiro quando o fim do arquivo é lido e não quando é encontrado [1]. Em outras palavras, feof não verifica se a última linha foi lida e sim se uma leitura após a última linha foi realizada.

O erro da duplicação ocorre porque quando fgets lê a última linha, feof ainda retorna falso, permitindo o loop ser executado novamente. Nesse último passo do loop, fgets realiza uma leitura no fim do arquivo (= EOF, End Of File) sem obter novos dados, e com isso, o conteúdo da última linha no buffer de linhas é usado novamente.

Solução 1

A primeira solução é testar se fgets realizou uma leitura correta verificando seu valor de retorno. Quando EOF é encontrado, essa função retorna NULL.

...
    while(!feof(arq)) {
        if(fgets(buf, BUFSIZ, arq) != NULL) {
            printf("%s", buf) ;
        }
    }
...

O if impede que o buffer seja usado após uma falha da fgets.

Solução 2

Essa solução é a mais usada. Como estamos usando feof e em seguida a fgets, podemos utilizar somente esta última função para o controle do loop.

...
    while(fgets(buf, BUFSIZ, arq) != NULL) {
         printf("%s", buf) ;
    }
...

Usamos esse método quando a primeira coisa que fazemos em um loop é a leitura das linhas. Porém, se rotinas importantes devem ser feitas antes de fgets, opte pela solução 1.

O código correto do fonte apresentado fica assim:

[leituralinhas.c]
#include <stdio.h>

int main() {
    FILE *arq ;
    char buf[BUFSIZ] ;

    arq = fopen("/etc/passwd", "r") ;

    while(fgets(buf, BUFSIZ, arq) != NULL) {
        printf("%s", buf) ;
    }

    fclose(arq) ;
    return 0 ;
}

Usando:

$ gcc -o leituralinhas leituralinhas.c
$ ./leituralinhas | tail
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
backuppc:x:992:988::/var/lib/BackupPC:/sbin/nologin
postgres:x:26:26:PostgreSQL Server:/var/lib/pgsql:/bin/bash
squid:x:23:23::/var/spool/squid:/sbin/nologin
chrony:x:991:987::/var/lib/chrony:/sbin/nologin
distcache:x:94:94:Distcache:/:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin
daemonio:x:1000:1000:Marcos Paulo:/home/daemonio:/bin/bash

Referências

[1] Things to Avoid in C/C++ — feof(), Part 3 by WaltP (Acessado em: Outubro/2012)
http://www.gidnetwork.com/b-58.html

6 pensamentos sobre “Última linha duplicada com feof+fgets

  1. Estou com este problema, porém estou usando o “fscanf”, pois cada leitura no arquivo preciso gravar em umas 7 variáveis.
    Este é o trecho
    while(!feof(txt)){
    fscanf(txt,” %d %s %f %f %f %f %f”,&dados.cod,&dados.nome,&dados.quant,&dados.valor,&dados.v_venda,&dados.v_total,&dados.lucro);
    i++;
    gotoxy(2,i);
    printf(“%d %s\t%.2f%5.2f%6.2f%9.2f%8.2f”,dados.cod,dados.nome,dados.quant,dados.valor,dados.v_venda,dados.v_total,dados.lucro);
    }

    • Olá,

      você pode usar a fscanf diretamente como condição ou colocar um “if” após o fscanf para parar o loop.

      while(fscanf(txt,” %d %s %f %f %f %f %f”,&dados.cod,&dados.nome,&dados.quant,&dados.valor,&dados.v_venda,&dados.v_total,&dados.lucro) != EOF)
      {
      }

      O fscanf() retorna EOF quando o fim do arquivo é lido. Para embelezar o código você pode colocar o fscanf() em uma função e usá-la como condição.

Deixe um comentário