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

De MediaWiki do Campus São José
Revisão de 15h26min de 22 de maio de 2015 por Douglas (discussão | contribs) (Criou página com '=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 ...')
(dif) ← Edição anterior | Revisão atual (dif) | Versão posterior → (dif)
Ir para navegação Ir para pesquisar
A versão imprimível não é mais suportada e pode ter erros de renderização. Atualize os favoritos do seu navegador e use a função de impressão padrão do navegador.

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.

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) 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()).


Exemplos

Exemplo 1
Escrevendo e lendo um arquivo texto de forma formatada

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

#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 fois 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;
  }

  for (i = 0; i<10;i++) {
      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ícios
  1. Implementar uma função que soma duas matrizes fornecidas em dois arquivos texto separados: MatA.dat e MatB.dat. Colocar o resultado em um arquivo chamado 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);       
}
Exemplo2
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 que fputc recebe o caracter como um inteiro mas realiza um cast para unsigned char com fins de escrevê-lo no arquivo.

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 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).

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 21 <>