En este tutorial vamos a hacer un programa en C que copie un fichero. Al hacerlo aprovecharemos para ver lo básico de lectura y escritura de ficheros binarios en C.
En linux tenemos dos grupos de funciones para lectura y escritura de ficheros. Las funciones open(), write(), read() y close() son de algo más bajo nivel y específicas de linux, así que no las usaremos en este ejemplo. En su lugar usaremos fopen(), fwrite(), fread() y fclose(), que son estándar de C y están presentes en todos los compiladores.
Lo primero de todo es abrir el fichero. Eso de abrir no es nada más que decirle al sistema operativo que prepare todo lo necesario para trabajar con un fichero concreto. Tendremos que abrir tanto el fichero que queremos leer como uno nuevo en el que escribiremos la copia del primero.
Un fichero tradicionalmente puede abrirse en modo texto o modo binario. No hay diferencia real entre uno y otro, salvo que en un fichero en modo texto se supone que hay fines de línea y en un momento dado hay funciones de ficheros que pueden buscarlos. Si se abre en modo binario, la información puede ser de cualquier tipo y las funciones de ficheros no buscarán fines de línea. Digo "tradicionalmente" porque en la práctica no hay ninguna diferencia entre abrirlo de una manera o de otra. Por convención, si el fichero es de texto legible se abre en modo texto. Si el fichero no es de texto legible, se abre en modo binario.
La función que nos permite abrir un fichero es fopen(). Sus parámetros son:
La función fopen() nos devuelve un FILE*. Esto no es más que un puntero a una estructura que contiene información sobre el fichero recién abierto. Normalmente podemos ignorar esa información, pero debemos hacer dos cosas con ese FILE* devuelto:
Para nuestro ejemplo, abriremos el fichero original como lectura y el fichero en el que haremos la copia como escritura. Puesto que nos da igual si el fichero es de texto o no, lo abriremos como binario, ya que es más general (un fichero de texto, con fines de línea, no es más que un caso particular de un fichero binario, que tiene bytes).
#include <stdio.h>
...
FILE *f1, *f2;
/* Apertura del fichero original, para lectura
en binario*/
f1 = fopen ("fichero.dat", "rb");
if (f1==NULL)
{
perror("No se puede abrir fichero.dat");
return -1;
}
/* Apertura del fichero de destino, para
escritura en binario*/
f2 = fopen ("fichero2.dat", "wb");
if (f2==NULL)
{
perror("No se puede abrir fichero2.dat");
return -1;
}
Ya está. f1 es el fichero origen y f2 es la copia.
Para leer bytes de un fichero, sin importarnos si hay o no fines de línea, podemos usar la función fread(). Esta función tiene los siguientes parámetros
La función fread() devuelve el número de elementos leidos, que debería ser igual a nitems.
El código de lectura puede ser así
/* Para meter lo que vamos
leyendo del fichero */
char buffer[1000];
/* Para guardar el número de items
leidos o si ha habido error */
int leidos;
...
/* Lectura e if para detectar posibles errores
*/
leidos = fread (buffer, 1, 1000, f1);
if (leidos == ...)
Para escribir un fichero binario, usamos la función fwrite(). Los parámetros son los mismos que fread(), pero con las siguientes salvedades
La función fwrite() nos devuelve el número de elementos escritos en el fichero o -1 si ha habido algún error.
En nuestro ejemplo, teniendo en cuenta que el número de elementos que tenemos disponibles en el array es el "leidos" que nos guardamos de la función fread(), el código de escritura quedaría así
fwrite (buffer, 1, leidos, f2);
Ya sabemos lo básico para leer y escribir. Como queremos hacer una copia, debemos leer el fichero de entrada hasta que se termine e ir escribiendo en el fichero de salida. Para ello, debemos hacer un bucle hasta el final de fichero en el que se lean datos de un lado y se escriban en otro.
Aunque no la usaremos, hay una función feof(FILE *stream) a la que pasándole el dichoso FILE* del fichero, nos devuelve 0 si hay datos para leer u otro valor si el fichero se ha terminado. Por ello, un if bastaría para saber si hemos llegado al final de fichero
if (feof(f1))
/* Hemos llegado al final de fichero */
O mejor todavía, se puede usar feof() como condición del bucle
while (!feof(f1))
{
/* Hay datos disponibles para leer */
}
Hay un pequeño problema que suele ser metedura de pata común para los principiantes. Hasta que no hagamos una lectura, NO podemos saber si se ha terminado el fichero. La función fopen() sólo abre el fichero, no comprueba si hay datos o no. La función feof() no mira el fichero, sino simplemente si la últma lectura ha llegado o no al final del fichero. Si usamos feof() ANTES de hacer una lectura (con fread() en nuestro caso), SIEMPRE obtendremos que hay datos disponibles.
Por ello, es necesario hacer un fread() (o cualquier otra función de lectura) ANTES de empezar el bucle.
leidos = fread (buffer, 1, 1000, f1);
while (!feof(f1))
{
/* Tratar los datos leídos
en buffer */
...
/* Y hacer otra lectura */
leidos = fread (buffer, 1, 1000, f1);
}
Parece un poco feo, pero tampoco es elegante usar un do...loop, ya que deberíamos meter un if entre medias
do
{
leidos = fread (buffer, 1, 1000, f1);
/* Ahora hay que tratar
los datos, pero si hemos llegado a final de fichero, NO hay datos que tratar.
Hay que poner un fi para este caso */
if (!feof(f1))
{
/* Tratar
los datos leídos */
...
}
} while (!feof(f1))
En fin, es cuestión de gustos y puedes usar la opción que más te guste o cualquier otra que se te ocurra. Lo importante es saber que hay que hacer al menos una lectura antes de utilizar feof().
Dije, de todas formas, que no iba a usar feof(). ¿Por qué?. Imagina que hay 10 bytes en el fichero y sólo 10 bytes. Cuando haga esta lectura
leidos = fread (buffer, 1, 1000, f1);
while (!feof (f1))
{
/* tratar los datos leidos */
}
se leerán los 10 bytes y leidos tendrá 10. Pero como hemos llegado a final de fichero, feof() será cierto. Si me fio de feof() para terminar, no entraré en el bucle y no trataré esos 10 bytes.
En nuestro ejemplo, haremos el bucle mientras leidos sea mayor que 0. Si es cero, se ha acabado el fichero, si es -1 ha habido algún error.
leidos = fread (buffer, 1, 1000, f1);
/* Mientras se haya leido algo ... */
while (leidos!=0)
{
/* ... meterlo en el fichero
destino */
fwrite (buffer, 1, leidos, f2);
/* y leer siguiente bloque
*/
leidos = fread (buffer, 1, 1000, f1);
}
Cuando terminamos de usar un fichero, hay que cerrarlo con fclose(FILE *). Unas tonterías sobre fclose() para que las tengas en cuenta.
Cuando se termina nuestro programa, el sistema operativo cierra todos los ficheros que tengamos abiertos. En principio, para un programa tonto como este, fclose() puede no ponerse sin problemas.
Sin embargo, conviene acostumbrarse a ponerlo para no meter la pata en programas más grandes y complejos. Hay dos posibles problemas si no se pone fclose().
Así, que siguiendo las buenas constumbres...
fclose(f1);
fclose(f2);
Vamos ahora a un caso un poco más complejo, cómo acceder a cualquier posición del fichero de golpe.