AULA 22 - Programação 1 - Graduação

De MediaWiki do Campus São José
Ir para: navegação, pesquisa

Objetivos

  • O sistema de arquivos no Linux;
  • Tratamento de arquivos no C;
  • O acesso com funções de alto nível.


O Sistema de Arquivos no Linux

O Arquivo

Um arquivo é um conjunto de dados que pode ser referenciado por um nome e pode ter outros atributos tais como: permissão para leitura e escrita, data da criação, data da última modificação etc.

Note que um arquivo pode conter dados como por exemplo: um programa C, um programa executável, textos, música, vídeos e fotos. Seja qual for a natureza dos dados o armazenamento será na forma de bits.

Normalmente, arquivos são armazenados em memórias secundárias, tais como CD e HD (Hard Disk). Também podem ser armazenados na memória principal RAM ou uma memória rápida FLASH.

Quanto a forma como os dados são armazenados, podemos dizer que os arquivos são binários ou texto. Qualquer um deles armazena bits em que em conjunto de 8, representam os bytes, que por sua vez estão associados a um código ASCII.

Sistema de Arquivos

Um sistema tal como o Linux possui milhares de arquivos. Para que arquivos possam ser acessados e armazenados de forma consistente, eles são organizados em um sistema de arquivos.

Tipicamente, um sistema de arquivos ocupa uma área de um disco (ou mídia de armazenamento). Nesta área ficam armazenados blocos de armazenamento dos dados dos arquivos e também as estruturas chamadas de inodes.

Um inode é um estrutura que possui as propriedades do arquivo e ponteiros para os blocos que contém os dados do arquivo. Tipicamente um sistema de arquivos possui uma lista de inodes que permite "indexar" cada um dos arquivos do sistema de arquivos.

Existem vários formatos de sistema de arquivos, dependendo do sistema operacional. No linux os formatos mais conhecidos são: ext2, ext3, ext4 etc. No Windows os mais conhecidos são FAT32 e NTFS.

Um sistema de arquivos normalmente possui uma estrutura de dados inicial chamada de superbloco. O superbloco traz informações sobre o tamanho de blocos do sistema, o início da lista de inodes (/).


Sistema de arquivos
superbloco lista de i-nodes blocos dos arquivos


Diretórios

São arquivos especiais que contém basicamente uma lista contendo nome e inode correspondente dos arquivos que o diretório contém.

Em um sistema de arquivos, o diretório / é o diretório raiz do sistema, a partir do qual pode-se encontrar todos os demais arquivos.

Referência a um arquivo

A localização de um arquivo pode ser realizada pela referência absoluta, ou seja, desde o diretório / do sistema:

cat /etc/passwd

O comando cat tem por objetivo mostrar no terminal o conteúdo do arquivo passwd. Para que este arquivo seja encontrado na árvore de diretórios, deve-se fornecer a referência absoluta, desde o diretório /

Uma alternativa de acesso, é o uso da referência relativa ao diretório de trabalho. O conceito de diretório de trabalho é criado pelo interpretador de comandos (shell). Desta forma pode-se por exemplo fazer o comando:

cat passwd

O sistema procurará o arquivo passwd dentro do diretório de trabalho, que é referenciado armazenado pelo shell. Neste caso, o diretório de trabalho deveria ser /etc

cd /etc
cat passwd

No Linux/Unix tudo é arquivo

No Linux, qualquer referência/acesso ao hardware/dispositivos é realizada na forma de acesso a arquivo. Ver arquivos no diretório de dispositivos:

 ls -l /dev

Como consequência, a partir de um programa em C, é possível abrir e escrever/ler em um dispositivo (desde que se tenha autorização).

Acessando arquivos a partir de programas C

Acesso a arquivos: funções de baixo e alto nível

Em sistemas do porte do Linux e Windows, por questões de segurança e controle, todo acesso a arquivo é realizado através de código do sistema operacional. Desta forma, um programa que deseja acessar um arquivo deve gerar uma CHAMADA AO SISTEMA. O Linux, assim como outros sistemas operacionais, possui uma API (Application Programming Interface) bem definida, na forma de um conjunto de chamadas que permitem realizar uma série de funcionalidades (serviços) para os processos (programas em execução).

Um programa em C realiza uma CHAMADA AO SISTEMA com auxílio de funções da biblioteca C (a glib no caso do Linux). Duas categorias de funções para acesso a arquivo são disponibilizadas (ver detalhes aqui):

  • Funções de baixo nível: acessam diretamente o sistema;
  • Funções de alto nível: as funções intermediam o acesso, criando buffers no espaço de endereçamento do processo. As funções de baixo nível são invocadas para ler e escrever dados no buffer.

Acesso a arquivo em MODO TEXTO através de funções de alto nível

O acesso em alto nível é realizado usando uma estrutura do tipo FILE definida no stdio.h. Todo acesso passa inicialmente por abrir o arquivo (função fopen), ler/escrever (várias funções, tipo fread(), fwrite()) e fechar o arquivo (fclose()).

A tabela abaixo apresenta as principais funções da linguagem C para manipulação de arquivos:

Função Ação
fopen() Abre um arquivo.
fclose() Fecha um arquivo.
putc() e fputc() Escreve um caractere em um arquivo
getc() e fgetc() Lê um caractere de um arquivo.
fseek() Posiciona em um registro de um arquivo
fprintf() Efetua impressão formatada em um arquivo.
fscanf() Efetua leitura formatada em um arquivo.
feof() Verifica o final de um arquivo.
fwrite() Escreve tipos maiores que 1 byte em um arquivo.
fread() Lê tipos maiores que 1 byte de um arquivo.


Exemplos

Nos exemplos que se seguem, serão usadas as funções fopen e fclose, para abrir e fechar arquivo e fprintf() e fscanf() para leitura e escrita. A forma é similar ao printf e scanf(ver http://diuf.unifr.ch/pai/ip/tutorials/c/TC02_scanf.pdf), a não ser pelo fato de que a escrita e leitura é realizada no arquivo indicado por p_arq

Exemplo 1
Escrevendo e lendo um arquivo texto de forma formatada:
#include <stdio.h>

void main()
{
  FILE *p_arq;
  int i;
  int res;

  if ((p_arq=fopen("IFSC.txt", "w")) == NULL) {
     printf("Problemas na abertura do arquivo\n");
     return;
  }

  for (i = 0; i<10;i++) {
  /* A funcao fprintf devolve o número de bytes gravados  ou EOF se houve erro na gravação */
      if((res = fprintf(p_arq,"Linha %d\n",i))==EOF) {  					  	    
	         printf("Erro\n");
			   break;
      }
  }
  fclose(p_arq);
}

Note que se o arquivo IFSC.txt não existir, ele será criado. Para ver o que foi escrito faça:

cat IFSC.txt
#include <stdio.h>
 
void main()
{
  FILE *p_arq;
  int i,j;
  int res;
  char buff[100];
 
  if ((p_arq=fopen("IFSC.txt", "r")) == NULL) {
     printf("Problemas na abertura do arquivo\n");
     return;
  }
 
  while(1) {
      if((res = fscanf(p_arq,"%s %d",buff,&j))==EOF) {  					  	    
	         printf("Fim de leitura\n");
			   break;
      }
      printf("%s %d\n",buff,j);
  }
  fclose(p_arq);
}

Note que o fscanf se comporta de forma similar ao scanf. A função retorna o caracter EOF (end-of-file) quando não existe mais dados a serem lidos.

Exercício
Implementar uma função que some duas matrizes fornecidas em arquivos de texto separados: MatA.dat e MatB.dat. Colocando o resultado em um outro arquivo de texto com o nome MatC.dat.
#include <stdio.h>
 
void main()
{
  FILE *p_arq;
  int i,j,res;
  float MatA[5][5], MatB[5][5], MatR[5][5];
  
  /* Ler a matriz MatA do arquivo */
  if ((p_arq=fopen("MatA.dat", "r")) == NULL) {
     printf("Problemas na abertura do arquivo\n");
     return;
  }
  
  for (i =0;i<5;i++) {
      for (j=0;j<5;j++) {
      	if((res = fscanf(p_arq,"%f",&MatA[i][j]))==EOF){  					    
	         printf("Fim de leitura\n");
			 break;
      	}
      	printf("%f ",MatA[i][j]);
      }
      printf("\n");
  }
  fclose(p_arq);
  
  /* Ler a matriz MatB do arquivo */

  printf("\n\n");
      
  /* Ler a matriz MatB do arquivo */
  if ((p_arq=fopen("MatB.dat", "r")) == NULL) {
     printf("Problemas na abertura do arquivo\n");
     return;
  }
  
  for (i =0;i<5;i++) {
      for (j=0;j<5;j++) {
      	if((res = fscanf(p_arq,"%f",&MatB[i][j]))==EOF){  					    
	         printf("Fim de leitura\n");
			 break;
      	}
      	printf("%f ",MatB[i][j]);
      }
      printf("\n");
  }
  fclose(p_arq);
    
  /* Calcular a soma das matrizes e colocar resultado em MatR */

  for (i =0;i<5;i++) {
      for (j=0;j<5;j++) {
      	MatR[i][j]=MatA[i][j]+MatB[i][j];
      }
  } 
  printf("Resultado da Adição\n"); 
  for (i =0;i<5;i++) {
      for (j=0;j<5;j++) {
          printf("%f ", MatR[i][j]);  
      }
      printf("\n");      
  }  
  /* Armazenar MatR em arquivo */

  if ((p_arq=fopen("MatR.dat", "w")) == NULL) {
     printf("Problemas na abertura do arquivo\n");
     return;
  } 
  
  for (i =0;i<5;i++) {
      for (j=0;j<5;j++) {
         if((res = fprintf(p_arq,"%f ",MatR[i][j]))==EOF) {  					    
	         printf("erro\n");
			 break;
      	 }
      }
      fprintf(p_arq,"\n");
  }
  fclose(p_arq);       
}
Exemplo 2
Neste exemplo usaremos as funções fgetc e fputc para ler e escrever caracteres ASCII nos arquivos. Seja um programa que conta o número de ocorrências do caracter 'a' em um arquivo e reescreve o arquivo sem os "as":
#include <stdio.h>
#include <stdlib.h>
main()
{
   FILE *fp_fonte,*fp_destino;
   int x,cont=0;
                   
   if((fp_fonte=fopen("teste.dat","r")) == NULL) {
		puts("Não conseguiu abrir o arquivo\n");
		exit(1);
   }
   if((fp_destino=fopen("dest.dat","w")) == NULL){
		puts("Não conseguiu abrir o arquivo\n");
		exit(1);
   }

   /* note que fgetc retorna um inteiro contendo o ASCII lido */
   while ((x=fgetc(fp_fonte)) != EOF){
      if(x=='a')
         cont++;
      else
	 if((x=fputc(x,fp_destino))==EOF) {
		puts("Não conseguiu abrir o arquivo\n");
		exit(1);
	 }	
   }	
   printf("ocorrências de a = %d\n", cont);
   fclose(fp_fonte);
   fclose(fp_destino);
}
Note
  1. Que fputc recebe o caracter como um inteiro mas realiza um cast para unsigned char com fins de escrevê-lo no arquivo.
  2. Substitua 'a' por '\n' e veja o que acontece com o arquivo destino.


Exemplo 3
Neste exemplo vamos explorar o modo de abertura do arquivo para fazer um append (escrever no final do arquivo).
#include <time.h>
#include <stdio.h>
 
int main(void)
{
   time_t ltime;
   FILE *fp;
   int num;

   time(&ltime); 

   if ((fp=fopen("DATA.txt", "a")) == NULL) {
       printf("Problemas na abertura do arquivo\n");
       return;
   }
   if ((num = fputs( ctime(&ltime), fp )) != EOF ) {
       fclose(fp);
   } else {
       printf("Erro na escrita do arquivo!\n");
   }
}

Execute o programa da forma (suponha que se chame LOG_tempo):

 log_tempo
 cat DATA.txt
 log_tempo
 cat DATA.txt
 log_tempo
 cat DATA.txt  
 
Exercício
Substitua o modo de append por uma simples escrita (w). Reexecute o programa conforme especificado anteriormente.

Ainda funções de acesso a arquivos

A localização corrente do acesso a um arquivo pode ser modificada antes de uma leitura ou escrita. Tipicamente, quando se abre uma arquivo para leitura/escrita, o "cursor" de acesso é 0, ou seja início do arquivo. Se o arquivo for aberto em modo append, o "cursor" é posicionado no final. A cada acesso (leitura ou esrita), este cursor é incrementado conforme o número de dados lidos ou escritos.

É possível entretanto modificar a posição deste cursor, tipicamente com as funções fseek() e rewind(). (ver http://www.gnu.org/software/libc/manual/html_node/File-Positioning.html#File-Positioning)

Considere o programa abaixo que escreve um vetor de 100 inteiros em um arquivo.

#include <stdio.h>

int x[100];

void main()
{
  FILE *fp;

  fp = fopen("teste.dat","w");
  x[0]=32;
  x[90]=11;
  fwrite(x, sizeof(int),100,fp);
  fclose(fp);
}
Note
  1. Que o vetor, sendo uma variável global não inicialiazada, será zerado pelo procedimento de startup do programa. Entretanto, a posição 90 recebe um valor (11).
  2. Não é possível visualizar o conteúdo do arquivo porque a função fwrite() grava o arquivo em formato binário.

Agora observe o programa seguinte que lê especificamente a posição 90 e na sequência restabelece o cursor no início. Note que o fread lê somente um inteiro na posição corrente do arquivo. A posição corrente foi determinada por fseek e depois retornada para o início com rewind.


#include <stdio.h>

int y;

void main()
{
  FILE *fp;

  fp = fopen("teste.dat","r");
  fseek(fp, 90*sizeof(int), SEEK_SET);
  fread(&y, sizeof(int),1,fp);
  printf("y=%d\n", y);
  rewind(fp);
  fread(&y, sizeof(int),1,fp);
  printf("y=%d\n", y);
  fclose(fp);
}
Exercício
Considere o programa abaixo. Ele deve acessar uma tabela que se encontra em um arquivo binário. Cada item da tabela se apresenta conforme o registro TRegistro. Implemente a função LerTab e crie um programa para escrever uma tabela iniciada com uma tabela de 5 registros a fim de testar a função implementada.
#include <stdio.h>

struct TRegistro {
  char nome[30];
  int idade; 
} Registro, *pAux;

struct TRegistro *LerTab(int indice)
{
}

void main()
{
  pAux=LerTab(3);
  if (pAux!=0) {
      printf("Nome lido %s\n", pAux->nome);
  }
}



<< Aula 22 >>