5.5.2 Directorios y ficheros en C/C++
§1 Presentación
Las cuestiones relativas al manejo de directorios y ficheros C/C++, están emparentadas con el tratamiento de flujos de E/S, ya que en estos lenguajes, desarrollados originariamente en entornos Unix, el concepto de E/S está íntimamente ligado al concepto genérico de fichero ("File"); cualquier dispositivo externo desde/hacia el que pueda fluir información hacia/desde el programa.
Como muchas otras cuestiones relacionadas con las E/S, la gestión y control de almacenamientos externos (ficheros y directorios de disco) no forman parte de estos lenguajes, sino que se han relegado a una serie de utilidades en la Librería Estándar. Los que hayan utilizado otros lenguajes menos generales, más orientados a las aplicaciones "de gestión", que desde luego suelen hacer uso extensivo de ficheros de disco, sentirán una especial frustración por la escasez, desorganización y falta de uniformidad de las funciones proporcionadas por estas librerías [1]. En consecuencia, las "Suites" de desarrollo. Por ejemplo, C++Builder o Visual C++, suplen esta deficiencia proporcionando cierto número de funciones adicionales que completan las del Estándar, aunque con el grave inconveniente de la falta de portabilidad y de ser soluciones limitadas a plataformas específicas.
La situación es especialmente penosa en las librerías que podríamos denominar de bajo-nivel; heredadas de las funciones tradicionales C. C++ ha venido a aliviar en parte esta situación añadiendo en su Librería un conjunto de clases conocidas con el nombre genérico de iostreams, que se encargan de manejar los flujos de E/S de forma más cómoda para el programador ( 5.3). En este capítulo nos referimos a las primeras; a funciones de la librería C++ heredadas de su antecesor, ya que cualquier el programador C++ las utiliza puntualmente. Además su estudio encierra un gran potencial didáctico.
§2 Sinopsis del manejo de ficheros
En la mayoría de los casos, el manejo de ficheros de disco exige una "apertura" previa del fichero que se desea utilizar (el fichero puede ser creado en el momento de su apertura). En este momento se le asocia un manejador "handle", que puede ser una estructura FILE ( 4.5.5c) o un número (int). A partir de aquí, todas las operaciones sobre el fichero utilizan el handle como identificador. Finalmente, cuando se termina su utilización, el fichero es "cerrado". Excepcionalmente algunas funciones que solo aportan información sobre ficheros determinados por un path-name, no se requiere la "apertura" previa ni la existencia de un "handle".
La apertura de ficheros puede efectuarse de varias formas; cada una de estas "formas" establece ciertas condiciones sobre el fichero y el modo en que podrá ser utilizado a partir de ese momento. Por ejemplo: puede establecerse que el fichero solo será utilizado para lectura, o que se creará nuevo si no existe otro de igual nombre; o que si existe otro de igual nombre, será borrado y sustituido por otro vacío; que las operaciones de L/E serán binarias o de texto ( 5.3), Etc.
La tabla adjunta muestra las formas de apertura establecidas por la función fopen de la librería clásica (consulte en el manual de su compilador los detalles de esta y las demás funciones).
Modo | Descripción |
"w" | Crear para escritura. Si el fichero existe con anterioridad será sobreescrito. El modo (binario o texto) depende de la variable globlal _fmode. |
"w+" | Crear para lectura/escritura. Si el fichero existe con anterioridad será sobreescrito. Idem. Idem. respecto al modo. |
"wb" | Crear para escritura en modo binario. Si el fichero existe con anterioridad será sobreescrito. |
"w+b" | Crear para lectura/escritura en modo binario. Si el fichero existe con anterioridad será sobreescrito. |
"a" | Abrir en modo escritura para añadir al final. Si el fichero no existe es creado. Idénticas consideraciones sobre el modo (binario/texto) que en el primer caso (depende de _fmode). |
"ab" | Abrir en modo escritura binaria para añadir al final. Si el fichero no existe, es creado. |
"r" | Abrir para lectura. Si el fichero no existe se produce un error. El modo binario/texto depende de _fmode como en el primer caso. |
"r+" | Abrir un fichero existente para lectura y escritura. Si el fichero no existe se produce un error. Idem. Idem. respecto al modo. |
"rb" | Abrir para lectura en modo binario. Si el fichero no existe se produce un error. |
"r+b" | Abrir un fichero existente para lectura y escritura en modo binario. Si el fichero no existe se produce un error. |
Nota: las operaciones en modo texto suponen
ciertas conversiones de los caracteres leídos/escritos con los realmente
recibidos o grabados en el disco (de forma que no se lee exactamente lo que
existe en el disco y a la inversa). Los detalles dependen de la
implementación, pero se resumen en que durante los procesos de lectura, los
pares CR-LF son transformados en un solo carácter LF. Por contra, en
las operaciones de salida los caracteres LF son escritos como combinaciones
CR-LF. Además de esto, en las operaciones de lectura el carácter 26
decimal SUB (CTRL+Z) es interpretado como fin de fichero (EOF = End Of File), y las rutinas de
escritura en modo texto insertan este carácter al final.
Las operaciones de lectura/escritura a ficheros de disco exigen incluir en el
fuente las cabeceras <stdio.h> o <cstdio> (esta
última es la versión moderna de la primera), e implican ciertos parámetros mantenidos automáticamente por el
Sistema. Los más significativos para el programador son:
-
File-pointer (abreviadamente fp). Un puntero que señala el punto a partir del que se efectuará la próxima operación de L/E. Estas operaciones suponen la lectura o escritura de un número determinado de caracteres a partir de la posición inicial del file-pointer. Dependiendo de la "forma" en que sea abierto un fichero, el fp puede ser situado automáticamente en la posición 0 (comienzo) o al final. Las operaciones de L/E mueven automáticamente el fp hacia adelante en función del número de caracteres leídos/escritos, de forma que las sucesivas operaciones se efectúan siempre en posiciones a partir de la última. Cuando el fp alcanza el final del fichero (EOF) en operaciones de lectura, se alcanza un estado especial que puede ser interrogado por el programa (ver función feof ). También existen funciones que permiten situar el fp en un punto arbitrario del fichero para que las futuras operaciones se realicen a partir de dicha posición (fseek , fsetpos ).
-
Marca de error: Una señal de estado que indica si la última operación ha concluido satisfactoriamente o se ha producido un error.
-
Marca de fin de fichero EOF ("End of File"). Indica si la operación ha alcanzado o no el fin de fichero.
-
Marca de inicio de fichero BOF ("Begin of File"). Señala si se ha alcanzado el principio del fichero.
Los detalles de su comportamiento dependen de la plataforma, pero son importantes de conocer. Por ejemplo, si la marca de fin de fichero está activada, no pueden efectuarse ciertas operaciones en el disco hasta que sea quitada mediante una operación de reposicionado del fp.
§3 Resumen de funciones
Con objeto de proporcionar una visión general de las posibilidades al respecto, se incluye una tabla-resumen (no exhaustiva) con las principales funciones C/C++ para gestión de ficheros y directorios ofrecidas por el compilador Borland C++ (que coincide substancialmente con el "Visual" de MS).
Nota: algunas de las funciones expuestas no se refieren exclusivamente a operaciones con ficheros de disco. En realidad se refieren a flujos ("Streams") de E/S con dispositivos genéricos. Sin embargo, aquí las presentamos bajo la perspectiva de su utilización con L/E a ficheros de disco.
La tabla, incluye el nombre de la función; una breve descripción de su
funcionalidad; una indicación del tipo de "Handle" utilizado para
su manejo, y datos sobre su portabilidad.
-
Handle H: F indica que el handle utilizado o devuelto por la función es una estructura FILE ( 4.5.5c) [3]; I señala que es un número entero (int) [4]. Finalmente, la ausencia de tipo indica que es una función para manejo de directorios o que la función no se refiere al fichero mediante su manejador, sino mediante su identificador completo ("Path-name").
-
Descripción: la tabla no pretende ser un sustituto del manual del compilador. Solo una breve reseña que ayude a identificar rápidamente que función puede servir a nuestros propósitos. Junto a algunas entradas se han incluido referencias a otras funciones relacionadas. Las referencias a "discos". Por ejemplo: número de disco actual, se refieren al volumen lógico. Aunque también pueden ser otros dispositivos ("Current drive").
-
Compatibilidad: indica si la función comentada puede ser utilizada en UNIX; Windows32; ANSI C y ANSI C++.
§3.1 Tabla-resumen
Nombre | Descripción | H. | UNIX | W32 | C | C++ |
access | Determina la existencia del fichero y el tipo de acción posible sobre él (lectura, escritura, ejecución) (filelength , stat ) | - | X | X | - | - |
chdir | Cambiar el directorio actual (getcurdir , getcwd , getdisk , mkdir , rmdir ) | - | X | X | - | - |
chmod | Cambiar el tipo de acceso permitido a un fichero (Lectura, Escritura o L/E). Ver _rtl_chmod | - | X | X | - | - |
chsize | Cambiar (aumentar o disminuir) el tamaño de un fichero | I | - | X | - | - |
closedir | Cerrar un directorio-stream (opendir , readddir , rewinddir ) | - | X | X | - | - |
_creat | Crear un fichero nuevo definido por su path-name (umask ) | I | X | X | - | - |
creattemp | Crear un fichero de nombre único en un directorio (útil para ficheros temporales) | I | - | X | - | - |
dup | Crar un nuevo handle a partir de otro existente. El nuevo tiene en común con el antiguo: fichero asociado; fp, y modo de acceso. | I | X | X | - | - |
dup2 | Similar al anterior | I | X | X | - | - |
eof | Determinar si el handle ha alcanzado el final de ficheo ("End Of File") | I | - | X | - | - |
fclose | Cerrar un fichero | F | X | X | X | X |
_fdopen | Permite crear un nuevo flujo (definido por un handle F) y asociarlo con un handle I obtenido con _creat , dup , dup2 o open | F/I | X | X | - | - |
feof | Detectar si se ha alcanzado el final de fichero después de una operación de lectura | F | X | X | X | X |
ferror | Detectar si se ha producido error después de una operación de L/E en el fichero (perror ) | F | X | X | X | X |
fflush | Forzar el vaciado del buffer de salida ( 5.3.4), y consiguiente escritura de datos al disco (setbuf , setvbuf ). | F | X | X | X | X |
fgetc | Leer el próximo carácter del fichero | F | X | X | X | X |
fgetpos | Obtener la posición actual del fp de un fichero (es complementaria de fsetpos ) | F | - | X | X | X |
fgets | Leer un número de caracteres de un fichero | F | X | X | X | X |
findclose | Cierra cualquier handle y libera la memoria dinámica asociada con invocaciones previas a findfirst y findnext | - | - | X | - | - |
findfirst | Comienza la búsqueda de ficheros definidos por atributos o comodines en un directorio de disco (searchenv , searchpath , searchstr ) | - | - | X | - | - |
findnext | Continúa la búsqueda iniciada con findfirst | - | - | X | - | - |
filength | Obtener el tamaño en bytes de un fichero (stat ) | I | - | X | - | - |
_fileno | Obtener el número de manejador asociado a un handle tipo FILE. | F/I | X | X | - | - |
fnmerge | Construir un path-name a partir de sus componentes (fullpath , makepath , splitpath , fullpath ) | - | - | X | - | - |
fnsplit | Desmembrar un path-name en sus componentes (fnmerge , fullpath ) | - | - | X | - | - |
fopen | Abrir un fichero en diversos modos (lectura, escritura, crearlo si no existe, etc). open , freopen | F | X | X | X | X |
fprintf | Componer y formatear una una cadena de caracteres y escribir el resultado en un fichero | F | X | X | X | X |
fputc | Escribir un carácter en un fichero (similar a putc ) | F | X | X | - | - |
fputs | Escribir una matriz C ( 3.2.3f) en un fichero | F | X | X | X | X |
fread | Leer n items de tamaño t bytes de un fichero almacenándolos en un buffer (ver fwrite ). | F | X | X | X | X |
freopen | Asociar un nuevo fichero a un manejador (handle) previamente asociado a otro fichero abierto. open , fopen | F | X | X | X | X |
fscanf | Leer una serie de campos de un fichero formateando los datos recibidos | F | X | X | X | X |
fseek | Esta blecer una nueva posición para el fp de un fichero abierto (similar a rewind ; lseek ) | F | X | X | X | X |
fsetpos | Establecer el fp asociado a un fichero a una posición obtenida previamente con fgetpos | F | - | X | X | X |
fstat | Fijar datos (tamaño, último acceso, etc) relativos a un fichero o directorio. Inversa a stat | I | X | X | - | - |
ftell | Obtener la posición actual (bytes desde el origen) del fp de un fichero (fseek , tell , lseek ) | F | X | X | X | X |
fullpath | Convertir un path-name relativo en absoluto (fnsplit , makepath , splitpath ) | - | - | X | - | - |
fwrite | Escribir n items de tamaño t bytes de un buffer en un fichero (complementaria de fread ) | F | X | X | X | X |
getc | Lee el próximo carácter del fichero (similar a fgetc ) | F | X | X | X | X |
getcurdir | Obtener el nombre del directorio activo en un volumen lógico determinado (getdisk , getdrive , setdisk ) | - | - | X | - | - |
getcwd | Obtener el nombre del directorio activo actual incluyendo nombre del disco (getdisk , getdrive , setdisk ) | - | - | X | - | - |
getcdwd | Similar al anterior | - | - | X | - | - |
getdisk | Obtener el número del disco actual (getcurdir , getdrive , setdisk ) | - | - | X | - | - |
_getdrive | Obtener el número del disco actual (getcurdir , getcwd ) | - | ? | X | ? | ? |
getftime | Obtener los datos de fecha y hora de un fichero (complementaria de setftime ) | I | - | X | - | - |
lock | Bloquear un fichero. Junto con unlock es una interfaz con el sistema de bloqueo del SO | I | - | X | - | - |
locking | Bloquear y desbloquear una porción de un fichero | I | - | X | - | - |
lseek | Mueve el fp de un fichero a una nueva posición (tell , fseek , ftell ) | I | X | X | - | - |
_makepath | Construir un path-name a partir de sus componentes (fnmerge , splitpath ) | - | - | X | - | - |
mkdir | Crear un directorio (rmdir ) | - | X | X | - | - |
_mktemp | Crea un nombre de fichero único a partir de una plantilla de la forma "ABC..Exx.xxx rellenando los seis últimos caracteres (tmpfile , tempnam ) | - | X | X | - | - |
open | Abrir un fichero en diversos modos. fopen ; freopen , umask | I | X | X | - | - |
opendir | (closedir , readddir , rewinddir ) | - | X | X | - | - |
perror | Escribir en el buffer estándar de salida. | |||||
_pipe | Establece un fichero en memoria que puede servir para compartir datos entre procesos | I | - | X | - | - |
_popen | Crea un "pipe" con el procesador de comandos | - | X | - | - | |
putc | Escribe un carácter en un fichero | F | X | X | X | X |
read | Leer n bytes de un fichero y almacenarlos en un buffer. | I | X | X | - | - |
readdir | (closedir , opendir , rewinddir ) | - | X | X | - | - |
remove | Borrar un fichero definido por su path-name (es una macro que invoca a _unlink ) | - | X | X | X | X |
rename | Permite renombrar un fichero dentro de un directorio y/o moverlo de directorio | - | - | X | X | X |
rewind | Posicionar el fp al principio del fichero. Similar a fseek ,aunque con diferencias de matiz | F | X | X | X | X |
rewinddir | (closedir , opendir , readddir ) | - | X | X | - | - |
rmdir | Borrar un directorio (mkdir ) | - | X | X | - | - |
_rmtmp | Borrar los ficheros temporales previamente creados con tmpfile (mktemp ) | - | - | X | - | - |
_rtl_chmod | Obtener/cambiar los atributos del fichero (similar aunque más amplio que chmod ) | - | X | X | X | - |
_rtl_close | Cerrar un fichero previamente abierto con _rtl_creat | I | X | X | X | - |
_rtl_creat | Crear un fichero y abrirlo para L/E binario (pueden ser ficheros ocultos o de Sistema) | I | X | X | X | - |
_rtl_open | Abrir un fichero para L, E o L/E binario | I | - | X | - | - |
_rtl_read | Leer n bytes de un fichero | I | X | X | X | - |
_rtl_write | Escribir n bytes en un fichero | I | X | X | X | - |
_searchenv | Busca un fichero en un path de entorno; PATH, LIB, INCLUDE, etc. (findfirst , searchpath , searchstr ) | - | - | X | - | - |
searchpath | Buscar un fichero determinado en las direcciones señaladas por la variable de entorno del Sistema: PATH=... (searchenv , findfirst , searchstr ) | - | - | X | - | - |
_searchstr | Buscar un fichero en una lista de directorios (searchenv , searchpath , findfirst ) | - | - | X | - | - |
setbuf | Utilizar un buffer específico para las operaciones de E/S de un flujo en lugar del establecido por el Sistema. Los caracteres almacenados en el buffer permanecen en él hasta que es vaciado por alguna causa (fflush ) | F | X | X | X | X |
setdisk | Establecer el número del disco actual (getdisk , getcurdir , getcwd ) | - | - | X | - | - |
setftime | Establecer los datos de fecha y hora de un fichero (complementaria de getftime ; _utime ) | I | - | X | - | - |
setmode | Fijar el modo de L/E (binario/texto) de un fichero abierto previamente en otro modo | I | - | X | - | - |
setvbuf | Utilizar un buffer específico en lugar del proporcionado por el Sistema para operaciones de E/S asociadas a un fichero. Permite establecer la forma en que se manejará el buffer. | F | X | X | X | X |
_seplitpath | Desmembrar un path-name en sus componente (makepath ) | - | - | X | - | - |
stat | Obtener información (tamaño, último acceso, etc) relativa a un fichero o directorio. Inversa a fstat (filelength , access ) | - | X | X | - | - |
system | Invocar el intérprete de comandos del SO para ejecutar un comando | X | X | - | - | |
tell | Obtener la posición actual (bytes desde el inicio) del fp de un fichero (lseek , ftell ) | I | X | X | - | - |
_tempnam | Obtener un nombre de fichero no utilizado en un directorio. Es posible especificar los 5 primeros caracteres de este nombre; el resto son proporcionados aleatoriamente por el Sistema (tmpfile ). | - | X | X | - | - |
tmpfile | Crea un fichero temporal de nombre aleatorio y lo abre para lectura-escritura binaria. El fichero es destruido automáticamente al cerrarlo o al terminar el programa (rmtmp , tempnam , mktemp ) | F | X | X | X | X |
umask | Establecer los parámetros de L/E utilizados por las funciones open y _creat | - | - | X | - | - |
_unlink | Borrar un fichero definido por su path-name (ver remove ) | - | X | X | - | - |
unlock | Desbloquear un fichero previamente bloqueado con lock . | I | - | X | - | - |
_utime | Permite establecer la fecha/hora de modificación de un fichero a un valor determinado (setftime ) | - | X | X | - | - |
[1] Esta percepción es especialmente intensa si se ha tenido ocasión de utilizar alguna de las riquísimas colecciones de utilidades proporcionadas por algunos compiladores de aplicaciones de bases de datos. Personalmente el uso de las utilidades de ficheros y disco de la Librería Estándar me ha producido la sensación de utilizar un palo y una piedra para reparar un automóvil.
[3] Cualquiera de las funciones que utilizan una estructura FILE como manejador pueden ser también utilizadas con los "ficheros" estándar de E/S, que tienen la propiedad de ser abiertos automáticamente por el programa ( 5.3). Por ejemplo:
fputs("Hello world\n", stdout);
[4] Existe una utilidad (_fileno) que permite calcular el número I correspondiente a un handle F.