Artigo original: File Handling in C — How to Open, Close, and Write to Files

Se você já escreveu o programa helloworld em C antes, já sabe o básico sobre entrada e saída (I/O, input/output, em inglês) em um arquivo em C:

/* Um arquivo hello world simples em C. */
#include <stdlib.h>

// Importar as funções de IO.
#include <stdio.h>

int main() {
    // Este printf é onde toda a mágica do IO de arquivos acontece!
    // Legal, não é?
    printf("Olá, mundo!\n");
    return EXIT_SUCCESS;
}

A manipulação de arquivos é uma das partes mais importantes na programação. Em C, usamos um ponteiro de estrutura de um tipo de arquivo para declarar um arquivo:

FILE *fp;

C fornece várias funções integradas para realizar operações básicas de arquivos:

  • fopen() - criar um arquivo ou abrir um arquivo existente
  • fclose() - fechar um arquivo
  • getc() - ler um caractere de um arquivo
  • putc() - escrever um caractere de um arquivo
  • fscanf() - ler um conjunto de dados de um arquivo
  • fprintf() - escrever um conjunto de dados em um arquivo
  • getw() - ler um número inteiro de um arquivo
  • putw() - escrever um número inteiro em um arquivo
  • fseek() - definir a posição em um ponto desejado
  • ftell() - dar a posição atual no arquivo
  • rewind() - definir a posição como o ponto inicial

Abrindo um arquivo

A função fopen() é usada para criar um arquivo ou abrir um arquivo existente:

fp = fopen(const char filename,const char mode);

Existem vários modos de abertura de um arquivo:

  • r - abre um arquivo no modo leitura
  • w - abre ou cria um arquivo de texto no modo de escrita
  • a - abre um arquivo no modo de inclusão (append)
  • r+ - abre um arquivo nos modos de leitura e escrita
  • a+ - abre um arquivo nos modos de leitura e escrita
  • w+ - abre um arquivo nos modos de leitura e escrita

Aqui temos um exemplo de leitura de dados de um arquivo e de como escrever nele:

#include<stdio.h>
#include<conio.h>
main()
{
FILE *fp;
char ch;
fp = fopen("hello.txt", "w");
printf("Insira dados");
while( (ch = getchar()) != EOF) {
  putc(ch,fp);
}
fclose(fp);
fp = fopen("hello.txt", "r");

while( (ch = getc(fp)! = EOF)
  printf("%c",ch);
  
fclose(fp);
}

Você pode estar pensando, "Isso simplesmente imprime texto na tela. Como assim isso é entrada/saída de arquivos?"

A resposta não é óbvia para começar, precisando de um pouco de compreensão sobre como funciona o sistema UNIX. Em um sistema UNIX, tudo é tratado como um arquivo, ou seja, você pode ler e escrever neles.

Isso quer dizer que seu método printf, por exemplo, pode ser abstraído como um arquivo, já que tudo o que ele faz é escrever. Também é útil pensar nesses arquivos como streams, pois, como veremos mais tarde, é possível redirecioná-los com o shell.

O que isso tem a ver com o helloworld e com a entrada/saída de arquivos?

Ao chamar printf, você está simplesmente escrevendo em um arquivo especial, chamado stdout, abreviação de standard output (saída padrão). stdout representa a saída padrão, conforme decidido pelo seu shell, e que, geralmente, é o terminal. Isso explica o motivo de se estar imprimindo na tela.

Existem duas outras streams (ou seja, arquivo) disponíveis para você, stdin e stderr. stdin representa a entrada padrão (standard input, em inglês), que o shell geralmente associa ao teclado. stderr representa a saída de erro padrão (standard error), que o shell geralmente associa ao terminal.

Entrada/saída rudimentar de arquivos, ou como eu aprendi a montar os pipelines

Chega de teoria! Vamos logo escrever o código! O modo mais fácil de se escrever em um arquivo é redirecionar o stream de saída usando a ferramenta de redirecionamento de saída, >.

Se quiser incluir (append, em inglês), você pode usar >>:

# Isto terá como saída a tela...
./helloworld
# ...mas isto escreverá em um arquivo!
./helloworld > hello.txt

Sem surpresas, o conteúdo de hello.txt será

Olá, mundo!

Vamos supor que há um outro programa chamado greet, semelhante a helloworld, que cumprimenta alguém que tenha um nome que ele recebe:

#include <stdio.h>
#include <stdlib.h>

int main() {
    // Inicializa um array para guardar o nome.
    char nome[20];
    // Ler uma string e salvá-la em nome.
    scanf("%s", nome);
    // Imprimir a saudação.
    printf("Olá, %s!", nome);
    return EXIT_SUCCESS;
}

Em vez de ler a partir do teclado, podemos redirecionar stdin para que leia de um arquivo usando a ferramenta <:

# Escreva um arquivo contendo um nome.
echo Kamala > name.txt
# O comando a seguir lerá um nome do arquivo e o imprimirá com a saudação na tela name from the file and print out the greeting to the screen.
./greet < name.txt
# ==> Olá, Kamala!
# Se você quisesse imprimir a saudação em um arquivo, poderia usar ">".

Observação: esses operadores de redirecionamento se encontram no bash e em shells semelhantes.

O que ocorre de fato

Os métodos acima somente funcionam com os casos mais básicos. Se quiser fazer coisas maiores e melhores, provavelmente desejará trabalhar com os arquivos de dentro do próprio C em vez de usar o shell.

Para conseguir isso, use uma função chamada fopen. Ela recebe dois parâmetros de string: o primeiro é o nome do arquivo e o segundo é o modo.

O modo, basicamente, são as permissões (r para leitura, w para escrita, a para inclusão. Você também pode combiná-los. rw, por exemplo, representaria poder ler e escrever no arquivo. Existem mais modos, mas esses são os mais comumente usados.

Depois de ter um ponteiro FILE, você pode usar basicamente os mesmos comandos de entrada e saída que já usou, exceto pelo fato de que você precisa adicionar a eles o prefixo f e de que o primeiro argumento será o ponteiro do arquivo. Por exemplo, a versão de arquivo de printf é fprintf.

Aqui temos um programa chamado greetings (saudações, em inglês), que lê de um arquivo que contém uma lista de nomes e escreve as saudações em um outro arquivo:

#include <stdio.h>
#include <stdlib.h>

int main() {
    // Cria os ponteiros de arquivos.
    FILE *nomes = fopen("names.txt", "r");
    FILE *saudar = fopen("greet.txt", "w");

    // Conferir se está tudo certo.
    if (!names || !greet) {
        fprintf(stderr, "Erro ao abrir arquivo!\n");
        return EXIT_FAILURE;
    }

    // Hora das saudações!
    char nome[20];
    // Basicamente, siga lendo até não haver mais nada para ler.
    while (fscanf(nomes, "%s\n", nome) > 0) {
        fprintf(saudar, "Olá, %s!\n", nome);
    }

    // Ao chegar ao final, imprima uma mensagem no terminal para informar ao usuário.
    if (feof(nomes)) {
        printf("Saudações concluídas!\n");
    }

    return EXIT_SUCCESS;
}

Supondo que names.txt contenha os nomes abaixo:

Kamala
Logan
Carol

Após executar o arquivo greetings, o arquivo greet.txt terá:

Olá, Kamala!
Olá, Logan!
Olá, Carol!