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
Olá amigo, estou utilizando fscanf e está acontecendo exatamente o mesmo problema mas, a sulção aqui não está funcionando lá. pode me ajudar?
Qual seria o trecho de código? Está usando feof+scanf? A solução usando scanf() como condição do loop funciona também.
Só me loguei para te agradecer. Valeu pelo post.
Obrigado pelo comentário, amigo.
Eu habilitei comentários anonimos há algum tempo, mas parece que não estão funcionando. :D
[]’s
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.