CAPÍTULO 5 MANEJO DE ARCHIVOS Actualmente existen dos formas para manejar los archivos en lenguaje C, uno llamado de primer nivel (también llamado secuencial) y otro llamado de segundo nivel (también llamado tipo registro o de alto nivel). En el estándar UNIX tradicional, se diseño un sistema de archivos con buffer para operar sobre archivos textos y un sistema de archivos sin buffer para operar sobre archivos binarios. Finalmente el comité ANSI decidió que no había razón para que existiesen dos sistemas separados para el manejo de archivos por lo que el sistema de archivos sin buffer no forma parte del estándar propuesto. En la actualidad un gran número de compiladores soporta la operaciones de bajo nivel. En el nivel más bajo se considera el archivo como un conjunto de bytes continuos, esto sin tener en cuenta como se han grabado, un usuario puede leer la cantidad de bytes que desee no importando la posición en la cual se encuentren éstos. En el modo de más alto nivel se puede acceder a uno o varios registros, es decir, lo que se lee cada vez es una agrupación lógica. Las operaciones de primer nivel, como se las llama a las de nivel más bajo, son las más potentes. Se dice que estas operaciones son las primitivas del sistema: son las operaciones básicas. Las de segundo nivel se construyen a partir de éstas. 5.1. INSTRUCCIONES A BAJO NIVEL La siguiente tabla muestra las instrucciones de primer nivel para manejo de archivos. Todas estas funciones se encuentran definidas en el archivo io.h. Función read() write() open() close() lseek() unlink() Descripción Lee un buffer de datos Escribe un buffer de datos Abre un archivo en disco Cierra un archivo Busca un byte especificado Elimina un archivo del directorio Tabla 5.1. Funciones para manejo de archivo en bajo nivel. Apertura de archivo int open(char *nomfich,int modo) /* devuelve un numero de archivo */ Si la operación ha resultado sin fallas, open devuelve el número que ha adjudicado al archivo que acaba de abrir. Devuelve un -1 en el caso de que se produzca un error. Para esta función el modo puede tener los siguientes valores: 44 Preparado por Juan Ignacio Huircán Modo 0x01 0x02 0x04 Efecto Sólo Lectura Sólo Escritura Lectura/Escritura O_RDONLY O_WRONLY O_RDWR Tabla 5.2. Modos de acceso de archivo. Sin embargo, si el archivo no existe, se producirá un error. Para especificar el modo se debe recurrir entonces a otras configuraciones de bits. Definidas en fcntl.h, los principales son los siguientes: Modo O_APPEND O_CREAT O_TRUNC O_TEXT O_BINARY Descripción (0x0800) (0x0100) (0x0200) (0x4000) (0x8000) Escritura al final del archivo Creación si el fichero no existe Vacía un archivo que ya existe Considera loa archivo como texto. Considera los archivos como binarios, no existen caracteres especiales. Tabla 5.3. Modos de apertura de archivo. Cuando se vaya a crear un archivo debe especificarse un tercer argumento. Este tercer argumento indica el modo de protección, los cuales están definidos en sys\stat.h Modo de protección Descripción S_IWRITE S_IREAD S_IREAD | S_IWRITE Sin protección contra escritura. Sin protección contra lectura. Lectura y escritura autorizada. Tabla 5.4. Modos de protección. Ej 5.1. Abriendo un archivo #include <stdio.h> #include <io.h> void main() { int f, modo=0x01; if( (f= open("Nombre1", modo))==-1) { printf("NO se puede abrir ! "); } } Para este ejemplo el archivo NOMBRE1 debe existir. Si NO existe la función open devuelve -1. Apuntes de Herramientas de Programación 45 Ej 5.2. Abriendo un archivo que no existe. #include <stdio.h> #include <io.h> #include <fcntl.h> void main() { int f, modo=0x04; /* Se puede usar O_RDWR en vez de modo */ if( (f= open("Nombre1", modo|O_CREAT))==-1) { printf("NO se puede abrir ! "); } else printf(" Archivo Abierto !"); } Para este ejemplo si el archivo NO existe lo crea. El acceso será de lectura-escritura. Todo lo que escriba en el archivo borrará la información existente. Si el modo es O_RDONLY , sólo se podrá leer. Para proteger los archivos, se pueden utilizar los modos de protección indicados, estos deben pasarse a la función open como un tercer argumento. Lectura de archivos int read(int fn, char *buf,int nbytes); Lee el número de caracteres especificados por nbytes, contados a partir de la posición actual en el archivo identificado por el número que contiene fn. Los bytes leídos se almacenan en la zona de memoria apuntada por buf. La función devuelve el número de caracteres que realmente se hayan leído. Si se produce un error devuelve -1. Si el número de caracteres leídos es inferior al número de caracteres pedidos significa que se ha llegado al final del archivo. Cada vez que se efectúa la operación read, se incrementa en nbytes el puntero de posición actual en el archivo. Escritura de archivos int write(int fn,char *buf,int nbytes); Escribe el número de caracteres especificados por nbytes, los que se deben encontrar en la zona apuntada por buf, a partir del puntero de la posición actual del archivo. Esta función devuelve el número de caracteres realmente escritos. En el caso de que se produzca un error devuelve un -1. Posicionamiento de un archivo long lseek(int fn, long desp1,int modo); 46 Preparado por Juan Ignacio Huircán Devuelve el nuevo valor del puntero de posición actual en un archivo, o sea el desplazamiento en número de bytes (en formato long) relativo al principio del archivo. Si ocurre un error retorna -1L (-1 en formato long). Cambia el valor del puntero de posicionamiento en el archivo. El desplazamiento es siempre relativo a una posición que depende del modo. Modo 0 1 2 Descripción Principio del archivo Posición actual en el archivo Al final del archivo Tabla 5.4. Modos de posicionamiento Se recalca que el desplazamiento es un valor long, es decir: lseek(5, 180L, 0); Se está accesando el archivo 5, a 180 caracteres respecto del principio. La definición de esta función se encuentra el prototipo io.h. Cierre de archivos int close(int fn); Siempre deben cerrarse aunque el sistema los cierra al final de la ejecución del programa. Ej 5.3. Ejemplos de apertura de archivos Un archivo se puede crear con las ordenes open, creat o creatnew . Cuando se trabaja en DOS para la creación de un archivo se debe especificar un tercer argumento. open("NOMBRE", O_WRONLY|O_CREAT|O_TRUNC|O_BINARY,S_IREAD|S_IWRITE); Crea un archivo con nombre "NOMBRE", en modo binario. Si el archivo ya existiera, será borrado el antiguo. No tiene ninguna protección (se podrá borrar sin ningún problema). open("NOMBRE", O_WRONLY|O_CREAT|O_TRUNC|O_BINARY,S_IWRITE); Igual al caso anterior, pero en este caso el archivo está protegido contra escritura. open("NOMBRE", O_WRONLY|O_APPEND|O_TEXT,S_IREAD,S_IWRITE); Se abrirá un archivo "NOMBRE" tipo texto, y el puntero se posicionará al final del archivo, escribirá a continuación. open("NOMBRE", O_CREAT|O_RDONLY|O_TEXT); Apuntes de Herramientas de Programación 47 Se abre el archivo "NOMBRE" tipo texto si no existe lo crea, en caso contrario lo abre pero no se modifica el escribir sobre él. 5.2. INSTRUCCIONES DE SEGUNDO NIVEL (ALTO NIVEL) En las instrucciones de segundo nivel los archivos ya no se designan por un número, sino, por un puntero a una estructura compleja llamada FILE cuya descripción se haya en el archivo stdio.h. Apertura de archivo FILE *fopen(char *nombrefich, char *modo); fopen devuelve un puntero a una estructura FILE. En el caso de existir un error devuelve NULL. El modo se especifica mediante una cadena de caracteres. Modo "r" "w" "a" "r+" Descripción Para lectura solamente. Para escritura solamente (si el archivo ya existiera lo borra). Añadir al final de un archivo que ya existe. Actualizar uno que ya existe. Tabla 5.5. Modos de apertura en alto nivel. Ej 5.4. Para abrir un archivo binario o texto #include <stdio.h> void main() { FILE * fp; fp=fopen("NOMBRE", "wb"); ... } #include <stdio.h> void main() { FILE * fp; fp=fopen("NOMBRE", "wt"); ... } Lectura de archivo int fread(char *p,int s,int n, FILE *fp); Lee n registros de tamaño s del archivo indicado por fp y los almacena en la zona de memoria indicada por p. fread devuelve el número de registros leidos, si este número es menor que el pedido, se ha llegado al final del archivo. Para mayor precisión se usa el feof. 48 Preparado por Juan Ignacio Huircán Escritura de archivo fwrite(char *p,int s,int n, FILE *fp); Escribe n registros de tamaño s del archivo indicado por fp. Los registros se encuentran almacenados en la zona de memoria indicada por p. La función devuelve el número de registros realmente escritos. Cerrar archivo int fclose(FILE *fp); Posicionamiento en un archivo fseek(FILE *fp, long pos, int modo); /* Análoga a lseek */ Condición de fin de archivo int feof(FILE *fp); Posicionamiento al comienzo de un archivo rewind(FILE *fp); Ej 5.5. Creación de un archivo de registros. #include <stdio.h> #include <string.h> void main() { FILE *fp; struct {char nombre[10]; int edad;}persona; char buf[65]; fp=fopen("PERSONAS", "wb"); printf("\nNOMBRE :?"); gets(&buf[0]) wihile(buf[0]) { strcpy(persona.nombre,&buf[0]); printf("\nEDAD :?"); gets(&buf[0]); sscanf(&buf[0],"%d",&persona.edad); fwrite((char *)&persona,12,1,fp); printf("\nNOMBRE :?"); gets(&buf[0]) } fclose(fp); } Apuntes de Herramientas de Programación 49 Ej 5.6. Creación de un archivo ASCII. #include <stdio.h> #include <string.h> void main() { FILE *fp; struct {char nombre[10]; int edad;}persona; char buf[65]; fp=fopen("PERSONAS", "wt"); printf("\nNOMBRE :?"); gets(&buf[0]) wihile(buf[0]) { strcpy(persona.nombre,&buf[0]); printf("\nEDAD :?"); gets(&buf[0]); sscanf(&buf[0],"%d",&persona.edad); fprintf(fp,"%12s %d\n",persona.nombre,persona.edad); printf("\nNOMBRE :?"); gets(&buf[0]) } fclose(fp); } 5.3. FORMAS ADICIONALES DE ENTRADA/SALIDA Una de la forma muy común de ingreso de datos es la que se realiza por teclado, a continuación se dan algunos ejemplos, ya que indirectamente ha sido referenciadas. Funciones para lectura de caracteres desde teclado y funciones a la salida estándar Estas funciones se encuentran definidas en conio.h y stdio.h. Funciones int getch(); int getche(); cputs(char *str); char *cgets(char *str); Descripción Lee caracter desde teclado Lee caracter desde teclado e imprime en pantalla Imprime una cadena de caractéres en pantalla Lee un string. str[0] especifica el largo, str[1] el tamaño del string leido y apartir de str[2] se encuentra la cadena leida. Retorna &str[2]. Tabla 5.6. Funciones para lectura de caracteres. 50 Preparado por Juan Ignacio Huircán Ej 5.7. Utilizando funciones para imprimir caracteres en pantalla. #include <conio.h> #include <string.h> void main() { char s[30]={"XXX"}; int i; clrscr(); puts(&s[0]) getch(); } A continuación se indican algunas funciones definidas en stdio.h Funciones Descripción char *gets(char *s); fgets(char *s,int n, FILE * fp); fputs(char *s,int n,FILE *fp); Lee un string desde el stdin hasta que se ingresa un caracter nueva línea (\n) retorna un puntero a s. Realiza la misma operación pero desde un archivo. si fue bien realizada restorna un puntero a s, sino un NULL. Escribe una cadena en un archivo. Tabla 5.7. Funciones defindas en stdio.h. Funciones de E/S con formato int printf(const char *format...); int scanf(const char *format...); /* Imprime en pantalla con formato */ /* Lee de teclado con formato */ int fprintf(FILE *fpconst char *format...); int fscanf(FILE *fp,const char *format...); /* Imprime en un archivo */ /* Lee desde un archivo */ Para las funciones cada format se especifica con % seguido de un caracter que indica el tipo de conversión (type), todo entre comillas ("). También se pueden intercalar entre ambos otros especificadores. % [flag][width][.prec][F|N|h|l] type Apuntes de Herramientas de Programación type d i o u x f e c s 51 Formato de salida Entero decimal con signo Entero decimal con signo entreo octal sin signo decimal sin signo Hexadecimal sin signo Punto flotante[-]dddd.ddd Punto flotante [-]d.ddde [+/-] ddd Caracter Cadena de caracteres Tabla 5.8. Especificación de tipo para el formato. [flag] Descripción [width] nada - Justificación a la derecha Justificación a la izquierda n 0n Descripción Relleno con n Caracteres en blanco Relleno con 0 Tabla 5.9. Descripción de [flag] y [width]. [.prec] nada .0 .n Descripción Precisión por defecto Precisión por defecto para d,i,o,u,x,e, E,f n decimales Tabla 5.10. Precisión. Ej 5.8. Imprimiendo datos en pantalla con formato #include <stdio.h> #include <conio.h> void main() { int entero =123; char caracter=37; float real=3.14159; char cadena[20]={"HOLA FLACO"}; clrscr(); printf("%d\n",caracter); printf("%c\n",caracter); printf("%d\n",entero); 52 Preparado por Juan Ignacio Huircán printf("%8d\n",entero); printf("%x\n",entero); printf("%f\n",real); printf("%4.3f\n",real); printf("%-8.4f\n",real); printf("%e\n",real); printf("%s\n",cadena); getch(); } Ej 5.9. Leyendo datos con formato desde teclado #include <stdio.h> #include <conio.h> void main() { int entero =123; float real=3.14159; clrscr(); printf("ENTERO : ?"); scanf("%d",&entero); printf("REAL: ?"); scanf("%f",&real); printf("%d %f\n",entero,real); printf("Pueden ser ingresados separdos por un espacio\n"); scanf("%d %f",&entero, &real); printf("%d %f\n",entero, real); getch(); } Ej 5.10. Escritura y lectura desde un archivo #include <stdio.h> #include <conio.h> void main() { int entero=34; float real=0.5; FILE *fp; fp=fopen("TEXTO.TXT","wt"); if(fp!=NULL) { fprintf(fp,"%d %f\n",45,34.56); fprintf(fp,"%d %f\n",40,3304.56); fprintf(fp,"%d %f\n",entero,real); fclose(fp); } printf("NO SE PUEDE ABRIR!\n"); getch(); } #include <stdio.h> #include <conio.h> void main() { int entero; float real; FILE *fp; fp=fopen("TEXTO.TXT","rt"); if(fp!=NULL) { fscanf(fp,"%d %f\n",&entero,&real); printf("%d %f\n",entero,real); fscanf(fp,"%d %f\n",&entero,&real); printf(fp,"%d %f\n",entero,real); fscanf(fp,"%d %f\n",&entero,&real); printf(fp,"%d %f\n",entero,real); fclose(fp); }printf("NO SE PUEDE ABRIR!\n"); getch(); } Apuntes de Herramientas de Programación 53