UT11: Manejo de archivos en C

Medios de Almacenamiento

Tarjetas perforadas, Cintas




Medios de Almacenamiento

Discos Rígidos de primer generación


Discos rígidos removibles (Disk Pack).

Medios de Almacenamiento

Discos Magneticos (Flexibles/Rigidos)


Medios de Almacenamiento

Discos Opticos (CD/DVD/BlueRay),
y arreglos de discos


The Life of a Data Byte: Storage as time pass by (1951-2009).

Medios de Almacenamiento

Memorias Flash (Pendrives, SDcard, MicroSD, etc...)


En 1956 el GB de disco duro salía a 9,2 millones de dólares.

Medios de Almacenamiento

Las cintas hoy...



24 PB in a standard 19-inch rack. LTO8 tapes have 12TB and go up to 30 TB compressed capacity.

Formato Físico

Sistema de Archivos

Analogía / Abstracción

    Archivo

    Carpetas

    Sistema de Archivos

Espacio usado vs. espacio de los archivos (I)


  • Discos disponibles en el sistema:lsblk
  • Estructura de la partición:fdisk /dev/sda1
  • Detalles del sistema de archivos:
    dumpe2fs /dev/sda1 | less

  • Espacio libre:df -k
  • Espacio "ocupado":du -bh
  • Espacio usado realmente:du -h

Espacio usado vs. espacio de los archivos (II)

  • ¿Donde se guarda la estructura del disco? ... ¡EN EL DISCO!
  • ¿Cuanto puedo direccionar dentro del disco? ... Depende del tamaño de cluster
  • ¿De que depende el tamaño de cluster? ... De mi FS y el tamaño del disco
  • Ejemplo:
    • 100 archivos * 20 bytes = 2000 bytes
    • Si tenemos un cluste de 1k (1024 bytes)
    • 100 archivos * 1k = 100 kbytes
    • Espacio usado = 100 kbytes, espacio de archivos 2000 bytes!!
    • ¿Alternativas? Zip files, particiones mas chicas...

Espacio FS vs Archivos


¿Cuanto espacio tiene un Libro?

  • Indice : Entradas en el FS (<1%)
  • Cuerpo : Archivos dentro del disco

Archivos

  • Persistencia: Guardar datos mientras la máquina está apagada. La memoria solo mantiene los datos con energía.
  • Estructura de disco, directorios y entradas de archivos:
    • Particiones
    • Sistemas de archivos (FAT, FAT32, NTFS, ext2, ext4, etc...)
  • Nodos:
    • Directorios
    • Links Simbólicos (ln)
    • Archivos

Archivos / Serializacion (I)

  • A guardar archivos en disco también se le dice serializar los datos.
  • Se le dice así por que tomaremos una estructura de datos en memoria con una forma particular y deberemos transformarla en datos que podemos guardar en "serie".
  • Para poder guardarlos vamos a tener que poner un byte "atrás" del otro para guardarlos en "linea". También podemos verlo como una conversión paralelo a serie. Los datos se encuentran en "paralelo" en memoria y los "serializaremos" en disco.

Archivos / Serializacion (II)

  • Esto también aplica a cuando enviemos los datos por una comunicación de datos. Esta comunicación podrá por ejemplo ser dentro de una red local, internet u otro tipo de red.
  • Lo importante de este proceso de "serialización" es respetar el orden en que se guardan los datos. Si de un entero de dos bytes guardamos primero el byte mas significativo y luego el menos significativo deberemos restaurarlos en el mismo orden.

Funciones de archivos (I)

  • Versiones Unix
    • Funciones: open, close, read, write, lseek y fcntl
  • Documentación con: man 2 {función}
    • Desventajas:
      • No usan buffer y esto hace mas lenta a las funciones de lectura y escritura.
      • Las funciones no son tan buenas manejando el fin de reglón (esto puede complicar su uso en plataformas no Unix).

Funciones de archivos (II)

  • Documentación con: man 2 {función}
    • Desventajas (sigue):
      • No se puede usar las funciones con formato:
        • fprintf() & fscanf()
      • Las funciones no son ANSI C
    • Ventajas:
      • No usan buffer
  • Librería ANSI C stdio.h
    • Mejor soportada, implementada, recomendada. Usa buffers lo que además la hace mas rápida.

Librería stdio.h

  • La estructura FILE
    • Es una estructura que contiene información que necesita C para poder manejar los archivos.
    • Las funciones de archivos usan este "descriptor de archivo" para poder hacer referencia a flujos/streams.

  • Buffers
    • Normalmente todo acceso a disco se hace usando buffers de memoria. Esto hace el acceso al disco mucho mas rápido.
    • La librería estándar incluye implementaciones usando buffers para la entrada y la salida.

Funciones de la librería stdio.h

  • Apertura / creación, y cierre de streams buffereados:
    • fopen() y fclose()
  • Entrada salida binaria:
    • fread() y fwrite()
  • Funciones de entrada/salida de a un carácter o de a una línea:
    • fgetc(), fgets(), fputc() y fputs()
  • Entrada y salida formateada:
    • fprintf() y fscanf()
  • Posicionamiento en un stream:
    • feof(), ftell(), rewind(), fseek(), fgetpos() y fsetpos()

Funciones Unix
de Archivos

open


$ man 2 open

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

flags: O_RDONLY, O_WRONLY & O_RDWR

mode: O_APPEND, O_CREAT, O_TRUNC, más...

close


$ man 2 close

#include <unistd.h>

int close(int fd);

read


$ man 2 read

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

write


$ man 2 write

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

lseek


$ man 2 lseek

#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);

whence: SEEK_SET, SEEK_CUR & SEEK_END

fcntl


$ man 2 fcntl

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );

cmd: F_DUPFD - Duplicating a file descriptor (y otros...)

Tamaño Archivo

Se podría usar lseek, pero tiene un int como tamaño.


#include <stdio.h>
#include <sys/stat.h>
 
int main() {
	char filename[] = "somefile.txt";
 
	struct stat st;
	stat(filename, &st);
	off_t size = st.st_size;
 
	printf("The file size id %d bytes.\n", size);

	return 0;
}

Ejemplos

FILE *fopen(const char *filename, const char *mode);

(I)


  • Abriendo Archivos
    • Antes de leer o escribir debemos abrir los archivos y conseguir el descriptor del archivo a usar.
  • Retorna:
    • Si exitoso el puntero al descriptor del nuevo archivo.
    • Si hay error un puntero nulo
  • Modos de apertura:
    • r: Solo lectura
    • w: Solo escritura

FILE *fopen(const char *filename, const char *mode);

(II)


  • a: Escritura al final del archivo o creado para escritura si no existia
  • r+: Abre un archivo para ser actualizado (lectura y escritura)
  • w+: Abre un archivo para lectura y escritura, pero si existia el archivo lo "pisa" (borra)
  • a+: Similar al modo "a", pero desplaza el puntero hasta el final del archivo


    Modo Binario o Texto:
  • Si se agrega una "b" al final (p/ej rb) C los interpreta como binarios.
  • Esto solo tiene sentido si la aplicación debe manejar archivos de windows y unix que tienen diferencias de formato en modo texto que C manejará por defecto e ignorará si usamos el modificador b.

int fclose(FILE *stream);

int feof(FILE *stream);


  • fclose() - Cerrando un archivo
    • Si es exitoso retorna 0, si no EOF (-1).
    • Al cierre se liberan los buffer y escribe todo en disco.
    • Uso:
      
      if(EOF==fclose(fp)) printf("Error guardando!");
      

  • feof() - ¿Estamos en el fin del archivo?
    • Retorna algo distinto de 0 (verdadero) si el puntero dentro del archivo llegó al final.
    • Retorna 0 si el archivo todavía tiene datos.

int fgetc(FILE *stream);

int fputc(int c, FILE *stream);


  • fgetc()/fputc():
    • Si exitoso retorna el carácter leído o escrito convertido (casteado) a un entero.
    • Si falla o llega al final del archivo retornará EOF

Ejemplo: ej1_v1.c

Archivo usado en el ejemplo miarch.txt



#include <stdio.h>

int main()
{
    FILE *file = fopen("miarch.txt", "r");

    int x;
    /* Lee un caracter en cada pasada desde el archivo terminando  */
    /* cuando encuentra EOF.                                       */
    while (( x = fgetc(file)) != EOF ) {
            printf( "%c", x );
        }

    fclose( file );
}

int fscanf (FILE *stream,
const char *format [, address, ...]);

int fprintf (FILE *stream,
const char *format [, argument, ...]);

  • Descripción:
    • Son las funciones de entrada y salida formateada, pero se pueden aplicar directamente a archivos
  • fscanf() retorna:
    • Si exitoso el numero de campos de entrada que pudo tomar, convertir y guardar.
    • Si retorna 0 es por que ningún campo pudo ser guardado
    • Si se llegó al final del archivo retornará EOF
  • fprintf() retorna:
    • Si exitoso en numero de bytes que pudo mostrar
    • Si hay error retornará EOF

Ejemplo: ej1_v2.c



#include <stdio.h>

int main()
{
    FILE *file = fopen("miarch.txt", "r");
    FILE *file2 = fopen("miarchout.txt", "w");

    int x;
    while (( x = fgetc(file)) != EOF ) {
        //printf( "%c", x );
        //fputc(x,file2);
        fprintf(file2,"%x ",(char) x);
        }

    fclose( file );
    fclose( file2 );
}

Otro ejemplo que
imprime en hexa: ej1_v3.c


char *fgets(char *s, int size, FILE *stream);

int fputs(const char *s, FILE *stream);


  • fgets():
    • Exitoso: El puntero al string s
    • Error o fin del archivo: NULL

  • fputs():
    • Si fue exitoso retorna un número positivo
    • Si falla retornará EOF

Ejemplo: ej2_1.c



#include <stdio.h>

int main()
{
   FILE *fp;
   char str[60];
   fp = fopen("file.txt" , "r");
   if(fp == NULL) {
      perror("Error opening file");
      return(-1);
   }
   if( fgets (str, 60, fp)!=NULL ) {
      puts(str);
   }
   fclose(fp);
   return(0);
}

Nota: El "if" no itera. El ej2_2.c es una versión con número de línea y que itera.

Mas ejemplos:

size_t fread(void *ptr, size_t size, size_t n,
FILE *stream);

size_t fwrite(const void *ptr, size_t size,
size_t n, FILE *stream);

(I)

  • Los argumentos:
    • ptr: Puntero al bloque de datos a leer o escribir
      (Recordar que "void *" es... ¡Casting!)
    • size: Tamaño de cada elemento a leer o escribir en bytes
    • n: Cantidad de elementos a leer
    • stream: Descriptor del archivo a leer o escribir
    • size_t: En este caso es la cantidad de elementos

size_t fread(void *ptr, size_t size, size_t n,
FILE *stream);

size_t fwrite(const void *ptr, size_t size,
size_t n, FILE *stream);

(II)


  • fread(): Leyendo datos de un archivo
    • Retorna:
      • Si es exitoso: La cantidad de elementos leídos
      • Si hay error EOF o 0 (elementos leídos)

  • fwrite(): Escribiendo datos a un archivo
    • Retorna:
      • Si es exitoso: La cantidad de elementos que pudo escribir
      • En error retorna un número mas bajo del solicitado o cero.

¿Como dejo de usar scanf()
para probar ejercicios?


  • ej5w.c: leo un archivo de texto,
    y escribo uno de datos.
  • ej5r.c: leo un archivo de datos.


Nota: Para el ejercicio usar el archivo de ejemplo misnum.txt

Flujos / Streams


  • Concepto de flujo de datos:
    • Serialización de datos

  • Tipos de stream conocidos:
    • stdin: Entrada estándar
      • teclado
    • stdout: Salida estándar
      • monitor
    • stderr: Error estándar
      • impresora


long ftell(FILE *stream);


  • ftell():
    • Retorna la posición actual en el archivo
    • Si el archivo es binario se calcula el desplazamiento en bytes desde el principio del archivo
    • El valor retornado por ftell() puede ser usado luego en llamados a fseek().
    • Retorna:
  • Si exitoso la posición dentro del archivo
  • Si hubo un error retorna un -1L (valor negativo)

void rewind(FILE *stream);


  • rewind():
    • Al terminar rewind() el archivo comenzará a leer o escribir al principio del archivo.
    • Es el equivalente de fseek(stream, 0L, SEEK_SET), pero también limpia los flag de EOF y error. fseek() solo limpia el flag de EOF.

int fseek(FILE *stream, long offset, int whence);

(I)


  • Mueve la posición dentro del archivo (modificando el descriptor de archivo).
  • Argumentos
    • stream: Descriptor del archivo al cual le queremos modificar la posición de lectura/escritura.
    • offset: Desplazamiento. Diferencia en bytes hacia la nueva posición. Si queremos saber donde ir podemos averiguarlo usando ftell() y movernos desde SEEK_SET.
    • whence: Uno de las tres posiciones (SEEK_xxx) desde donde desplazarnos definidas (0, 1, or 2)

int fseek(FILE *stream, long offset, int whence);

(II)


  • Retorna:
    • 0 si el movimiento se pudo hacer
    • Un valor distinto de 0
  • SEEK_xxx (define donde se comienza a moverse)
    • SEEK_SET (0) comienza al principio del archivo
    • SEEK_CUR (1) comienza a moverse desde la posición actual
    • SEEK_END (2) comienza el movimiento desde el final del archivo

Ejercicio



Calcular el tamaño del archivo: miarch.txt








Nota: Tienen algo así en una parte del ejemplo de copia de archivos (ej6.c).

int fgetpos(FILE *stream, fpos_t *pos);

int fsetpos(FILE *stream, fpos_t *pos);

(I)


  • Las funciones fgetpos() y fsetpos() trabajan en conjunto y permiten moverse por el archivo.
  • Son alternativas al uso de ftell() y fseek() (cuando usamos "whence" como SEEK_SET).
  • Uso:

    • Con fgetpos() obtendremos la posición actual
    • Con fsetpos() la modificaremos

int fgetpos(FILE *stream, fpos_t *pos);

int fsetpos(FILE *stream, fpos_t *pos);

(II)


    fpos_t: Tipo de dato dependiente de la implementación del compilador
  • DJGPP: typedef unsigned long fpos_t;
  • Borland & Dev-C++: typedef long fpos_t;
  • No está definido de manera cierta en Unix


Lo mejor es solo usar fpos_t entre ambas funciones,
ya que no se puede asegurar la portabilidad del dato.

funciones similares



fgetc(stdin); === getc();

fputc(stdout); === putc();

fprintf(stdout, ...); === printf(...);

fscanf(stdin, ...); === scanf(...);


ftell() === fgetpos()

fseek() === fsetpos()