Ya hemos visto unos ejemplos de acceso aleatorio a un fichero binario. Vamos ahora con algo más simple, algunos ejemplillos de ficheros de texto normalitos.
En primer lugar haremos una copia del fichero, usando las funciones fgets() y fputs().
Después algún ejemplo simple y otro algo más complicado de uso de la función fscanf(), para ver que tiene más posibilidades de las que habitualmente aparecen en los libros de iniciación.
Vamos a hacer una copia de un fichero de texto. Aunque podemos copiarlo con el mismo programa que hicimos en el ejemplo de fichero binario, puesto que un fichero de texto no es más que un caso particular de fichero binario, vamos a hacerlo aquí de otra forma, específica para ficheros de texto. Esto nos servirá de excusa para presentar las funciones fgets() y fputs().
Lo primero, como siempre, será abrir los dos ficheros. El original como fichero de lectura y la copia como fichero de escritura.
#include <stdio.h>
...
FILE *f = fopen("fichero.txt", "r");
FILE *f2 = fopen("fichero2.txt", "w");
if (f==NULL)
{
perror ("Error al abrir fichero.txt");
return -1;
}
if (f2==NULL)
{
perror ("Error al abrir fichero2.txt");
return -1;
}
Una de las posibles funciones que tenemos para leer un fichero de texto es fgets(). Esta función lee una línea completa del fichero de texto y nos la devuelve. Tiene los siguientes parámetros:
Esta función devuelve la misma cadena de caracteres que le pasamos si lee algo del fichero o NULL si hay algún problema (fin de fichero, por ejemplo).
Para leer todas las líneas consecutivamente del fichero, podemos hacer un bucle hasta que esa función fgets() nos devuelva NULL. Puede ser así
char cadena[100]; /* Un
array lo suficientemente grande como para guardar la línea más
larga del fichero */
...
while (fgets(cadena, 100, f) != NULL)
{
/* Aquí tratamos
la línea leída */
}
Lo de distinto de NULL es una forma como otra cualquiera de mirar cuando se termina el fichero. Si queremos podemos hacer alguna variante usando feof() o cualquier otra cosa que se nos ocurra.
Un detalle que aquí no es importante, pero que es posible que tengamos que tener en cuenta en otros casos. El caracter fin de línea (\n en linux, \r\n en windows) NO se incluye en la cadena leída. fgets() lo lee del fichero, pero NO lo copia en cadena, así que no lo tenemos. En su lugar pone en en cadena un fin de cadena \0 (recuerda que en C todas las cadenas se terminan con un byte cero, que se representa como \0)
Como queremos ir escribiendo estas líneas en otro fichero, usaremos la función fputs(). Esta función escribe la cadena en el fichero y le añade un fin de línea (\n en linux, \r\n en windows).
La función fputs() admite los siguientes parámetros
Esta función devuelve el número de caracteres escritos o FEOF (un entero definido en algún include) si hay algún problema.
Nuestro código completo de copia quedaría así
char cadena[100]; /* Un
array lo suficientemente grande como para guardar la línea más
larga del fichero */
...
while (fgets(cadena, 100, f) != NULL)
{
fputs(cadena, f2);
}
Ya sólo queda cerrar los ficheros
fclose(f);
fclose(f2);
Una de las funciones que explica cualquier libro para leer ficheros de texto es fscanf().
La idea de fscanf es que como parámetro se le pasa el formato de lo que se quiere leer, y esta se encarga de buscarlo en el fichero y leerlo. Los parámetros de esta función son
Esta función devuelve el número de variables que ha conseguido rellenar.
Por ejemplo, si queremos leer una cadena de texto del fichero, ponemos
char cadena[100];
fscanf (f, "%s", cadena);
%s indica que estamos buscando una cadena de caracteres delimitada por espacios, tabuladores o saltos de línea y que queremos que la guarde en el array cadena. Por supuesto, la f es que la lea del fichero f, que es la misma f del fopen() que pusimos antes, al principio.
Si queremos un número entero, podemos poner
int valor;
fscanf (f, "%d", &valor);
%d indica que queremos un número entero. Hay que pasar la dirección de una variable entera (&valor en este caso) para que guarde ahí el entero que lea. En el caso de un array (como el de la cadena anterior), no hace falta poner el & delante.
De igual manera, tenemos los siguientes formatos:
int valor;
fscanf (f, "%i", &valor);
%i Un entero. Si empieza por 0x se lee en hexadecimal, si empieza por 0 se lee en octal y si empieza por otra cifra se lee como decimal. Por ejemplo, si en el fichero hay 0xFF, en valor se meterá un 255. Si en el fichero hay 010, en valor se meterá un 8 (en octal, el 8 se representa como 10).
%o Un octal. Se lee un número que se interpreta como octal. Si en el fichero hay 10, en valor se meterá 8.
%u Para un entero sin signo. valor debería ser de tipo unsigned int.
%x, %X: Un hexadecimal. Si en el fichero hay FF, en valor se meterá un 255
%f, %e, %g, %E: Para números con decimales. valor debería ser tipo float.
Hasta aquí lo que puedes encontrar en cualquier libro. Sin embargo, hay muchas más posibilidades y vamos a ver una de ellas.
En fscanf() podemos poner una expresión más compleja para tratar de coger el formato que queramos., indicando exactamente qué caracteres queremos leer. Imagina, por ejemplo, que nuestro fichero de texto es así
campo1:2:campo3
campo4:5:campo6
es decir, en cada línea tres campos separados por un separador, dos puntos en este caso. El primer campo es de texto, el segundo es un número y el tercero es de texto. Los campos de texto están formados por letras minúsculas y cifras.
Podemos leer una línea de esas de golpe, obteniendo los tres campos por separado, así
char cadena[100];
char cadena2[100];
int valor;
...
fscanf (f, "%[a-z0-9]:%d:%[a-z0-9]\n", cadena, &valor, cadena2);
Pero ... ¿ Qué es eso que hemos puesto ?
Básicamente son los tres campos, si te fijas, verás los separadores de : puestos ahí. Lo demás es una "representación" de qué cosas tiene cada campo (letras minúsculas y cifras el primero y tercero, un número el segundo).
El del medio, que es el más fácil, es un %d, que según vimos antes, corresponde a un entero. Es decir, estamos diciendo que el segundo campo es un número.
Para el primero y el tercero hemos puesto algo tan raro como esto %[a-z0-9]. Veamos que siginifica.
Podemos decir qué tipo de caracteres son los que componen un campo. Eso se pone con % y entre corchetes los caracteres válidos. Por ejemplo, si ponemos %[abc] quiere decir que el campo puede tener letras a, b y c. Si ponemos en el fichero abackk, entonces un fscanf() con el formato indicado leería sólo hasta "abac", dejándose "kk" sin leer.
Para no tener que escribir muchas letras, de la a a la z en nuestro caso, el formato admite que pongamos un guión. Así %[a-z] significa que valen todas las letras, de la a a la z, minúsculas.
Como nuestro campo también tiene números (campo1, etc), tenemos que poner que también valen los dígitos. Para ello, simplemente los ponemos y nuevamente evitamos escribirlos todos con un guión. Entonces %[a-z0-9] quiere decir que está compuesto por letras minúsculas y cifras.
El \n que pusimos al final del tercer campo es para indicar que detrás del tercer campo va un fin de línea.
Resumiendo, %[a-z0-9]:%d:%[a-z0-9]\n significa un campo cuyo valor es de letras minúsculas y cifras, un separador dos puntos, un campo numérico, otro separador dos puntos y un tercer campo compuesto por letras minúsculas y cifras, que además es el último de la línea.
Como puedes ver, con la función fscanf() podemos leer fácilmente ficheros formateados de campos con separadores. De todas formas, este tipo de formatos es un poco delicadito y debemos estar muy seguros de que el fichero y el formato que pongamos coinciden exactamente. Si no es así, podemos leer valores muy raros y sin sentido.
Si echas un ojo al man de fsanf(), puedes ver más detalles sobre los posibles valores de este formato.
Si te interesa C++, puedes ver cómo leer y escribir ficheros de texto con C++.