Leitura de linhas de texto

Muitos programas recebem seus dados de algum grande arquivo. Convém organizar tais programas como um processo iterativo em que cada iteração tem três passos:

  1. lê uma linha do arquivo e converte-a em uma string;
  2. extrai da string as informações desejadas;
  3. processa as informações extraídas.

O passo 2 pode ser executado pela função sscanf da biblioteca stdio.  O passo 1 pode ser executado pela função fgets da biblioteca stdio.  Este capítulo oferece uma alternativa caseira para fgets que é mais confortável e mais poderosa.  (Trata-se de uma variante da função getline da GNU C Library.)

Sumário:

Arquivos de texto

Grande parte de nossos dados é armazenada em arquivos de texto (também conhecidos como plain text ou texto simples).  Num arquivo de texto, os bytes representam caracteresSuporemos que todos os nossos arquivos usam codificação UTF-8 e portanto cada caractere é representado por 1, 2, 3, ou 4 bytes consecutivos. (Convém lembrar que certos bytes não fazem parte do código de nenhum caractere; é o caso dos bytes no intervalo 248 . . 255, por exemplo.)  Se cada caractere do arquivo é representado por apenas 1 byte — ou seja, se todos os caracteres são ASCII — temos um arquivo ASCII.

Em geral, um arquivo de texto tem várias linhas, sendo o fim de cada linha sinalizado pelo byte \n.  Diremos que cada linha do arquivo é uma linha de texto(A propósito, o último byte de todo arquivo de texto bem educado deve ser um \n; além disso, nenhuma linha deve ter um espaço antes de \n.)

Leitura de uma linha de texto

Eric Roberts escreveu uma biblioteca simpio (veja a interface simpio.h e a implementação simpio.c) cujas funções tornam confortável a leitura de uma linha de texto.  A principal função da biblioteca é ReadLine, que substitui, com vantagem, a função fgets (mas talvez não seja tão rápida quanto aquela).

Segue uma versão ligeiramente modificada da função ReadLine de Roberts.  A função lê uma linha de comprimento arbitrário de um arquivo de texto.

#include <stdlib.h>
#include <string.h>
typedef unsigned char byte;


// A função readLine lê uma linha (a partir da posição 
// corrente) do arquivo de texto infile e devolve uma
// string com o mesmo conteúdo da linha. O byte \n que
// sinaliza o fim da linha não é armazenado como parte
// da string. A função devolve NULL se a posição
// corrente do arquivo estiver no fim do arquivo. Uso
// típico da função: 
//             s = readLine (infile);
// (Esta função é uma adaptação da função ReadLine da
// biblioteca simpio de Eric Roberts.)

byte *readLine (FILE *infile)
{
   int n = 0, size = 128, ch;
   byte *line;
   line = malloc (size + 1);
   while ((ch = getc (infile)) != '\n' && ch != EOF) {
      if (n == size) {
         size *= 2;
         line = realloc (line, size + 1);
      }
      line[n++] = ch;
   }
   if (n == 0 && ch == EOF) {
      free (line);
      return NULL;
   }
   line[n] = '\0';
   line = realloc (line, n + 1);
   return line;
}

A função transfere bytes do arquivo infile para o vetor line alocado dinamicamente. Toda vez que o vetor fica cheio, seu tamanho é dobrado.

A função readLine supõe, implicitamente, que a linha lida não contém bytes nulos (uma hipótese perfeitamente razoável).

Exercícios 1

  1. Compare a documentação de readLine com a da função fgets da biblioteca stdio.
  2. Como readLine se comporta se porventura encontrar um \0 antes de \n?
  3. A documentação de readLine não explica o que acontece se o arquivo infile não tiver um \n (entre a posição corrente e o fim do arquivo). Preencha essa lacuna.  (A propósito, é uma boa ideia terminar todo arquivo de texto com um \n.)
  4. Analise a seguinte variante de readLine, mais próxima da ReadLine de Roberts.  A função strncpy que aparece no código é uma variante da função strcpy: com argumentos nline, line, e n, ela copia os n primeiros bytes de line para nline.
       int n = 0, size = 128, ch;
       byte *line, *nline;
       line = malloc (size + 1);
       while ((ch = getc (infile)) != '\n' && ch != EOF) {
          if (n == size) {
             size *= 2;
             nline = malloc (size + 1);
             strncpy (nline, line, n);
             free (line);
             line = nline; }
          line[n++] = ch;
       }
       if (n == 0 && ch == EOF) {
          free (line);
          return NULL; }
       line[n] = '\0';
       nline = malloc (n + 1);
       strcpy (nline, line);
       free (line);
       return nline;
    
  5. No execício anterior, o que acontece se trocarmos strncpy (nline, line, n) por strcpy (nline, line)?

Leitura de um número inteiro

A função GetInteger da biblioteca simpio de Eric Roberts usa a função ReadLine para extrair um número inteiro de uma linha de texto digitada no teclado. Segue uma versão ligeiramente modificada da função:

// Lê uma linha de texto do teclado, extrai dela um
// número inteiro e devolve esse inteiro. Se a linha
// tiver algum byte não branco antes ou depois do
// inteiro a função dá ao usuário a oportunidade de
// tentar de novo. Uso típico: i = getInteger ();
// (Esta função é uma adaptação da função GetInteger
// da biblioteca simpio de Eric Roberts.)

int getInteger (void) {
   while (1) {
      byte *line = readLine (stdin);
      int value;
      byte ch;
      switch (sscanf (line, " %d %c", &value, &ch)) {
         case 1:
            free (line);
            return value;
         case 2:
            printf ("Byte inesperado: '%c'\n", ch);
         default:
            printf ("Tente de novo\n");
      }
      free (line);
   }
}

A função lê uma linha de texto e em seguida usa sscanf para extrair dela um inteiro. Ler uma linha completa é essencial para que a função possa se recobrar de um eventual erro de digitação do usuário; caso contrário, os bytes depois da ocorrência do erro permaneceriam no buffer de entrada e confundiriam as operações de entrada subsequentes.

A função sscanf da biblioteca stdio é como a função fscanf, exceto que opera sobre uma string em lugar de um arquivo. A função recebe uma string line e tenta extrair dela os objetos especificados pelo formato dado no segundo argumento. A função devolve o número de objetos que conseguiu extrair com sucesso de line.

Exercícios 2

  1. Estude as funções da biblioteca simpio de Eric Roberts.
  2. Estude a documentação da função sscanf da biblioteca padrão stdio.