Menu

Manipulação de arquivos em C: fopen, fclose e ler arquivos linha a linha

Como ler e escrever arquivos em C - a tabela de modos do fopen, verificar NULL, escrever com fprintf e fputc, ler com fgets, fscanf e fgetc, o laço canônico linha a linha e quando usar o modo binário.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Tudo o que um programa calcula desaparece quando ele termina, a menos que ele anote em algum lugar. A interface de arquivos do C vive em <stdio.h> - o mesmo cabeçalho do printf - e é deliberadamente parecida: um arquivo é um stream de bytes, e as funções que operam na tela têm gêmeas que recebem um stream como argumento.

O tipo do handle é FILE *. Você nunca olha dentro de um FILE; ele é um tipo opaco (veja typedef) e você só guarda um ponteiro para ele.

Os três passos

Toda operação com arquivo segue a mesma forma: abrir, usar, fechar.

A verificação de NULL não é paranoia opcional. O fopen falha sempre que o arquivo não existe (em modo de leitura), o diretório não é gravável, o caminho está errado, ou o processo ficou sem handles de arquivo - e cada um desses casos devolve NULL. Usar um handle NULL é uma falha de segmentação.

Mandar o erro para stderr em vez de stdout é a convenção: ele não é bufferizado e pode ser redirecionado separadamente da saída de verdade.

Modos do fopen

O segundo argumento é uma string curta de modo. Errá-la é o erro mais destrutivo desta área toda, porque "w" esvazia silenciosamente um arquivo existente.

ModoEscreveSe o arquivo existeSe não existe
"r"simnãoabre no iníciofalha, devolve NULL
"w"nãosimtruncado até ficar vaziocriado
"a"nãosimescreve só no fimcriado
"r+"simsimabre no iníciofalha, devolve NULL
"w+"simsimtruncado até ficar vaziocriado
"a+"simsimlê em qualquer lugar, escreve no fimcriado

Acrescente um b a qualquer um deles ("rb", "wb", "ab+") para o modo binário - mais sobre isso no fim.

Duas regras que evitam perda real de dados:

  • Use "r" quando a intenção é ler. "r+" em um nome de arquivo digitado errado falha de forma segura; "w+" cria um arquivo vazio e você não perdeu nada além de tempo. "w" no nome certo quando você queria "r" destrói os dados.
  • Use "a" para logs. Todo fprintf cai no fim, independentemente de onde o stream estava posicionado, que é exatamente o que um log quer.

Escrever e ler em um único programa

O editor abaixo executa uma ida e volta completa - cria um arquivo, escreve registros nele, fecha, reabre para leitura e imprime o que encontra.

fprintf e fscanf são o printf e o scanf com um stream como primeiro argumento; tudo sobre seus especificadores de formato é idêntico, inclusive a largura %31s que impede nome de transbordar.

A condição do laço é == 2, o número de itens que o formato pede. Testar contra EOF em vez disso é um bug clássico: uma linha malformada faz o fscanf devolver 0, não EOF, e o laço gira para sempre sobre uma entrada que não consegue consumir.

Ler linha a linha com fgets

O fscanf é conveniente para dados rigidamente formatados. Para arquivos de texto - configuração, logs, CSV, qualquer coisa que um humano escreveu - leia linhas inteiras. Este laço é o canônico:

O que faz do fgets o padrão certo:

  • Ele recebe o tamanho do buffer, então não pode transbordar. Passe sizeof linha e a chamada continua correta se você redimensionar o array.
  • Ele devolve NULL no fim do arquivo ou em caso de erro, o que dá uma condição de laço limpa.
  • Ele mantém a quebra de linha quando a linha coube no buffer. Isso é útil - nenhum '\n' no que você recebeu significa que a linha era maior que o seu buffer e o resto ainda está esperando. strcspn(linha, "\n") encontra o índice da quebra de linha (ou o tamanho da string se não houver nenhuma), então atribuir '\0' ali a remove nos dois casos.

Para distinguir um fim de arquivo de verdade de um erro, pergunte depois do laço:

if (ferror(in)) {
    fprintf(stderr, "erro de leitura\n");
} else if (feof(in)) {
    /* fim normal */
}

Não escreva while (!feof(fp)) como condição de laço. O feof só se torna verdadeiro depois de uma leitura já ter falhado, então esse laço processa o conteúdo final do buffer uma vez a mais. Teste o valor de retorno da função de leitura - como os dois laços acima fazem.

Um caractere por vez: fgetc e fputc

Para trabalho no nível de bytes - contar caracteres, transformar um arquivo, copiar - fgetc e fputc lidam com um caractere por chamada.

Um detalhe que importa: c é declarado como int, não char. O fgetc devolve um int para poder devolver todo valor de byte possível e a sentinela distinta EOF (que é -1). Guardá-lo em um char faz a comparação contra EOF ser sempre falsa ou falsamente verdadeira para o byte 0xFF, dependendo de char ser com sinal na sua plataforma. Esta é uma das armadilhas mais antigas do C.

Verificar erros do jeito certo

Uma leitura de produção fica assim:

O errno guarda um código descrevendo a última falha e o strerror o transforma em uma frase; o perror imprime a sua mensagem mais essa frase em uma única chamada. Eles precisam de <errno.h> e <string.h>, respectivamente.

O fclose também pode falhar - ele esvazia os dados bufferizados, e a escrita pode não caber no disco - então, para qualquer coisa importante, verifique:

if (fclose(fp) != 0) {
    fprintf(stderr, "falha ao esvaziar o buffer e fechar\n");
}

Modo binário em um parágrafo

O modo texto pode traduzir finais de linha (no Windows, \n vira \r\n na escrita e volta na leitura) e pode tratar certos bytes de forma especial. Para dados que não são texto - uma imagem, uma struct despejada literalmente, um blob comprimido - abra com b e use fread/fwrite, que movem bytes crus:

fwrite(ptr, tamanho, quantidade, fp) escreve quantidade itens de tamanho bytes e devolve quantos conseguiu; o fread é o espelho. Saiba que um arquivo escrito assim fica preso à máquina que o escreveu - preenchimento da struct, tamanho dos inteiros e ordem dos bytes todos vazam para os bytes - então serve para um cache ou um arquivo temporário e é errado para um formato que outros programas precisam ler.

Erros comuns

  • Não verificar se o fopen devolveu NULL. A falha que vem depois é atribuída à leitura, não à abertura.
  • Abrir com "w" quando você queria "r". O arquivo é esvaziado antes de você perceber.
  • Esquecer o fclose. A saída bufferizada se perde, e cada arquivo não fechado vaza um handle.
  • while (!feof(fp)). Processa a última linha duas vezes. Teste a chamada de leitura.
  • char c = fgetc(fp). Quebra a comparação com EOF. Use int.
  • fscanf("%s", buf) sem largura. O mesmo estouro de buffer do scanf no teclado.
  • Caminhos relativos. fopen("data.txt", "r") procura no diretório de trabalho, não ao lado do executável. Se um arquivo "some", geralmente é por isso.

Perguntas frequentes

Como abrir um arquivo em C?

FILE *fp = fopen("data.txt", "r"); abre um arquivo para leitura e devolve um handle FILE *, ou NULL se falhar. Sempre verifique se é NULL antes de usar o handle, e chame fclose(fp) quando terminar.

Quais são os modos do fopen em C?

"r" leitura (o arquivo precisa existir), "w" escrita (cria, ou trunca um arquivo existente deixando-o vazio), "a" append (cria, escreve no fim). Acrescentar + torna cada um de leitura e escrita: "r+", "w+", "a+". Acrescentar b ("rb", "wb") abre em modo binário.

Como ler um arquivo linha a linha em C?

Use fgets em um laço while: while (fgets(linha, sizeof linha, fp) != NULL) { ... }. Ele para em cada quebra de linha ou quando o buffer enche, devolve NULL no fim do arquivo, e não pode estourar porque você passa o tamanho do buffer.

Por que meu arquivo está vazio depois de escrever nele em C?

Na maioria das vezes você esqueceu o fclose. A saída é bufferizada, então os dados podem estar ainda na memória quando o programa termina de forma anormal. fclose esvazia o buffer e fecha; fflush(fp) esvazia sem fechar. A outra causa é abrir com "w" uma segunda vez, o que trunca o arquivo que você acabou de escrever.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR