Sign in Join
Wiki copytemporary

File Handling In Pascal/es: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: File Handling In Pascal/es

OldNewLine
1+## Descripción
2+
3+Algo que necesitan conocer todos los programadores es como trabajar con ficheros. Los ficheros se utilizan para almacenar datos de forma persistente, i.e. almacenar datos de manera que puedan ser retornados en un momento posterior sin tener que volver a crearlos. Los ficheros pueden utilizarse para almacenar configuraciones de usuario, reportes de error, medidas o resultados de cálculos, etc. Esta página explica lo básico sobre manejo de ficheros.
4+
5+## Estilo del procedimiento antiguo
6+
7+Cuando se utilizan ficheros en el modo clásico de Pascal (no orientado a objetos) se puede utilizar el tipo **TextFile** (o simplemente **Text**) para almacenar texto, que está estructurado típicamente en líneas. Cada línea finaliza con una marca de fin de línea ([[End of Line|EOL=End Of Line]]). En este tipo de fichero se pueden almacenar tanto cadenas (strings) como números (integer, real...) formateados además de la forma que más nos convenga. Estos ficheros pueden posteriormente abrirse para visualizarlos o editarlos mismamente con el IDE de Lazarus o cualquier otro editor de texto.
8+
9+Para propósitos específicos se puede crear un tipo de fichero personalizado que puede almacenar únicamente un tipo de dato. Por ejemplo:
10+
11+``` pascal
12+...
13+type
14+ TIntegerFile = file of integer; // Permite escribir únicamete números de tipo entero (integer) al fichero.
15+ TExtendedFile = file of extended; // Permite escribir úncamente números de tipo real al fichero.
16+ TCharFile = file of char; // Permite escribir únicamente caracteres simples al fichero.
17+ TPCharFile = file of PChar; // Permite escribir únicamente PChar al fichero.
18+ TByteFile = file of byte; // Permite escribir únicamente bytes al fichero.
19+...
20+```
21+
22+### Manejo de errores de Entrada/Salida
23+
24+El [I/O error handling flag](http://www.freepascal.org/docs-html/prog/progsu38.html#x45-440001.2.38) o flag (bandera) de Entrada/Salida (I/O -\> Input/Output) indica al compilador como manejarse con las situaciones de error: lanzar una excepción o almacenar el resultado de la entrada/salida en la variable IOResult.
25+
26+El flag de manejo del error de entrada/salida es una directiva del compilador. Para habilitarlo/deshabilitarlo escribimos:
27+
28+``` pascal
29+{$I+} // Los errores generarán una excepción EInOutError (por defecto)
30+{$I-} // Suprime los errores de entrada/salida: chequea la veriable IOResult para saber su código de error.
31+```
32+
33+Mediante la supresión de los errores de entrada/salida utilizando ({\$I-}) los resultados de la operación con ficheros se meterán en la variable IOSResult. Este es un [[Cardinal|cardinal (number) type]]. Cada uno de los distintos numeros indicarán los diferentes errores obtenidos. Estos códigos de error se pueden consultar en la documentación [1](http://www.freepascal.org/docs-html/rtl/system/ioresult.html).
34+
35+### Procedimientos de fichero
36+
37+Estas funciones y procedimientos de manejo de ficheros se encuentran en la unit 'system'. Ver la documentación de FPC para más detalles: [Referenccia sobre la unidad 'System'](http://www.freepascal.org/docs-html/rtl/system/index-5.html).
38+
39+- **AssignFile** (previene del uso del antiguo procedimiento **Assign** ) - Asigna un nombre a un fichero.
40+- **Append** - Abre un fichero ya existente en el modo añadir al final del mismo.
41+- **BlockRead** - Lee un bloque de datos de un fichero sin tipo poniéndolo en memoria.
42+- **BlockWrite** - Escribe un bloque de datos desde la memoria hacia un fichero sin tipo.
43+- **CloseFile** (previene el uso del procedimiento antiguo **Close** ) - Cierra un fichero que está abierto.
44+- **EOF** - Chequea si se ha llegado al final del fichero (EOF=End Of File), devuelve true si ha llegado y false si todavía no.
45+- **Erase** - Borra un fiechro del disco.
46+- **FilePos** - Retorna da la posición en que nos encontramos dentro del fichero.
47+- **FileSize** - Retorna el tamaño del fichero.
48+- **Flush** - Escribe los buffers de fichero a disco.
49+- **IOResult** - Retorna el resultado de la última operación de entrada/salida (I/O) de ficheros.
50+- **Read** - Lee desde un fichero tipo texto.
51+- **ReadLn** - Lee desde un fichero tipo texto (una línea entera) y salta a la siguiente línea.
52+- **Reset** - Abre un fichero para lectura.
53+- **Rewrite** - Crea un fichero para escritura.
54+- **Seek** - Cambia a una posición dentro del fichero.
55+- **SeekEOF** - Sitúa la posición dentro del fichero en su final.
56+- **SeekEOLn** - sitúa la posición del fichero al final de la línea.
57+- **Truncate** - Trunca el fichero en la posición indicada.
58+- **Write** - Escribe una variable en el fichero.
59+- **WriteLn** - Escribe una variable a un fichero de texto y salta a una nueva línea.
60+
61+### Ejemplo
62+
63+Un ejemplo completo de manejo de un fichero de texto del tipo **TextFile**: (es aconsejable utilizar estos ejemplos con el IDE de FreePascal o bien creando una aplicación de consola, nos facilitará así ver los mensajes de WriteLn. En este último caso nos crea automáticamente una estructura de programa en la cual tendremos que insertar el código en las secciones correspondientes por ejemplo donde pone { add your program here }.
64+
65+``` pascal
66+program CreateFile;
67+
68+uses
69+ Sysutils;
70+
71+const
72+ C_FNAME = 'textfile.txt';
73+
74+var
75+ tfOut: TextFile; // tipo fichero de salida.
76+
77+begin
78+ // Establece el nombre del fichero que vamos a crear
79+ AssignFile(tfOut, C_FNAME);
80+
81+ // Habilitamos el uso de excepciones para interceptar los errores (esto es como está por defecto por lo tanto no es absolutamente requerido)
82+
83+ {$I+}
84+
85+ // Embebe la creación del fichero en un bloque try/except para manejar los errores elegántemente.
86+
87+ try
88+
89+ //crea el fichero, escribe algo de texto y lo cierra.
90+ ReWrite(tfOut);
91+ WriteLn ('A continuación escribimos algo de texto al fichero ya que ponemos en WriteLn tfOut como salida');
92+ WriteLn(tfOut, '¡Hola textfile!');
93+ WriteLn(tfOut, 'Esto que se escribe directamente al fichero, no aparece por pantalla: ', 42);
94+
95+ CloseFile(tfOut); // Hemos terminado de escribir el texto en el fichero, por tanto le cerramos.
96+
97+ except
98+ // Si ocurre algún error podemos encontrar la razón en E: EInOutError
99+ WriteLn('A continuación imprimimos en pantalla mediante E.ClassName / E.Message:');
100+ WriteLn('Ha ocurrido un error en el manejo del fichero. Detalles: ', E.ClassName, '/', E.Message);
101+ WriteLn ('Por otro lado podemos imprimir en pantalla el valor de la variable IOResult que es: ',IOResult);
102+
103+ // Tambien podemos ponerlo de la forma:
104+ // on E: EInOutError do
105+ // begin
106+ // Writeln('Ha ocurrido un error en el manejo del fichero. Detalle: '+E.ClassName,'/',E.Message);
107+ // end;
108+
109+ // Da información y espera por la pulsación de una tecla.
110+ writeln('Fichero ', C_FNAME, ' creado si todo fue bien. Presiona Enter para finalizar.');
111+ ReadLn;
112+end.
113+```
114+
115+Ahora abre el fichero generado utilizando para ello cualquier editor y verás justamente el texto que hemos escrito mediante código. Puedes probar el manejo de errores ejecutando el programa de nuevo, en esta ocasión la prueba que puedes realizar es establecer el atributo del fichero en modo solo lectura de forma que el programa no pueda hacer el rewrite y ver el error que nos lanza. Los atributos del fichero los podemos modificar mediante la función **FileSetAttr** (unidad **sysutils**) y consultarlos mediante **FileGetAttr**.
116+
117+Las constantes para los atributos de FileGetAttr y FileSetAttr son:
118+
119+- faReadOnly: solo lectura.
120+- faHidden: oculto (en Linux los encontramos en los ficheros que comienzan con un punto).
121+- faSysFile: sistema.
122+- faVolumeId: etiqueta de volumen.
123+- faDirectory: directorio.
124+- faArchive: archivo.
125+
126+Adicionalmente es interesante echar un vistazo a la función [FileOpen](http://lazarus-ccr.sourceforge.net/docs/rtl/sysutils/fileopen.html) donde lista los modos de fichero que acepta, así como otras funciones relacionadas, las cuales tienen su equivalencia a las listadas más arriba.
127+
128+Nota que el manejo de excepciones se ha utilizado como una manera fácil de realizar múltiples operaciones con ficheros y manejar los errores que obtengamos como resultado. También puedes utilizar {\$I-}, pero en este caso hay que consultar el valor almacenado en la variable IOResult después de cada operación y modificar tu próxima operación para tratar el posible error.
129+
130+Lo siguiente muestra como se puede añadir texto a un fichero del tipo textfile (al final del contenido ya existente):
131+
132+``` pascal
133+program AppendToFile;
134+
135+uses
136+ Sysutils;
137+
138+const
139+ C_FNAME = 'textfile.txt';
140+
141+var
142+ tfOut: TextFile;
143+
144+begin
145+ // Establece el nombre del fichero que va a recibir más texto.
146+ AssignFile(tfOut, C_FNAME);
147+
148+ // Embebe el manejo del fichero en un bloque try/except para gestionar los errores de manera elegante.
149+ try
150+ // Abre el fichero en la modalidad añadir (appending), escribe algo de texto y lo cierra.
151+ append(tfOut);
152+
153+ WriteLn(tfOut, ' Hola de nuevo fichero de texto. ');
154+ WriteLn(tfOut, 'El resultado de 6 * 7 = ', 6 * 7);
155+
156+ CloseFile(tfOut);
157+
158+ except
159+ on E: EInOutError do
160+ writeln('Ha ocurrido un error en el manejo del fichero. Detalles: ', E.Message);
161+ end;
162+
163+ // Da información y espera a que se pulse una tecla.
164+ WriteLn('Fichero ', C_FNAME, ' puede contener más texto. Presiona Enter para finalizar.');
165+ ReadLn;
166+end.
167+```
168+
169+Leyendo un fichero de texto (textfile):
170+
171+``` pascal
172+program ReadFile;
173+
174+uses
175+ Sysutils;
176+
177+const
178+ C_FNAME = 'textfile.txt';
179+
180+var
181+ tfIn: TextFile;
182+ s: string;
183+
184+begin
185+ // Da algo de información
186+ WriteLn('Leyendo el contenido del fichero: ', C_FNAME);
187+ WriteLn('=========================================');
188+
189+ // Establece el nombre del fichero a leer.
190+
191+ AssignFile(tfIn, C_FNAME);
192+
193+ // Embebe el manejo del fichero en un bloque try/except para manejar errores de manera elegante.
194+ try
195+ // Abre el fichero en la modalidad de lectura.
196+ ReSet(tfIn);
197+
198+ // Se mantiene leyendo líneas hasta alcanzar el final del fichero.
199+ while not eof(tfIn) do // Mientras no fin de fichero haz...
200+ begin
201+ ReadLn(tfIn, s); // Lee una línea de texto desde el fichero.
202+ WriteLn(s); // Escribe la línea de texto leida anteriormente mostrándola en pantalla.
203+ end;
204+
205+ // Realizado, por tanto procedemos a cerrar el fichero.
206+ CloseFile(tfIn);
207+
208+ except
209+ on E: EInOutError do
210+ WriteLn('Ha ocurrido un error en el manejo del fichero. Detalles: ', E.Message);
211+ end;
212+
213+ // Espera la intervención del usuario para finalizar el programa.
214+ WriteLn('=========================================');
215+ WriteLn('fichero ', C_FNAME, ' fue probablemente leido. Presiona Enter para finalizar.');
216+ ReadLn;
217+end.
218+```
219+
220+## Estilo de Objetos
221+
222+Adicionalmente al antiguo estilo de rutinas de manejo de ficheros mencionada anteriormente, existe un nuevo sistema que utiliza el concepto de streams (una corriente de datos o flujo de datos) a un nivel de abstracción superior. Esto significa que los datos pueden ser leidos o escritos a cualquier ubicación (disco, memoria, puertos de hardware, etc.) por un interface uniforme.
223+
224+Asociado a esto tenemos que la mayor parte de las clases manejadoras de cadenas tienen la habilidad de cargar y salvar contenidos desde/hacia un fichero. Estos métodos usualmente tienen los nombres SaveToFile y LoadFromFile. Otro montón de objetos (tales como las grids o rejillas de Lazarus) tienen funcionalidades similares, incluyendo los datasets de Lazarus (DBExport). Vale la pena echar un vistazo a la documentación o al código fuente antes de tratar de crear rutinas propias de salvado/carga.
225+
226+### Ficheros Binarios
227+
228+Para abrir ficheros en modo de acceso directo se puede utilizar TFileStream. Esta clase encapsula los procedimientos de sistema FileOpen, FileCreate, FileRead, FileWrite, FileSeek y FileClose que residen en la unidad **SysUtils**.
229+
230+[rutinas de entrada/salida (I/O)](http://www.freepascal.org/docs-html/rtl/sysutils/ioroutines.html)
231+
232+Fijate en el ejemplo de abajo como se encapsula el manejo de ficheros con un bloque try..except de forma que los errores se manejan correctamente justamente como sucedia con las rutinas clásicas equivalentes (para no enrevesar el ejemplo el fsOut.write no se pone dentro del bloque try...finally).
233+
234+``` pascal
235+program WriteBinaryData;
236+{$mode objfpc}
237+
238+uses
239+ Classes, Sysutils;
240+
241+const
242+ C_FNAME = 'binarydata.bin';
243+
244+var
245+ fsOut : TFileStream;
246+ ChrBuffer: array[0..2] of char;
247+
248+begin
249+ // Establecemos algunos datos aleatorios para su almacenamiento.
250+ ChrBuffer[0] := 'A';
251+ ChrBuffer[1] := 'B';
252+ ChrBuffer[2] := 'C';
253+
254+ // Interceptamos los errores en caso de que el fichero no pueda crearse.
255+ try
256+ // Creamos la instancia del flujo de datos,escribimos en el mismo y lo liberamos para evitar pérdidas de memoria.
257+ fsOut := TFileStream.Create( C_FNAME, fmCreate);
258+ fsOut.Write(ChrBuffer, sizeof(ChrBuffer));
259+ fsOut.Free;
260+
261+ // Manejamos los errores
262+ except
263+ on E:Exception do
264+ writeln('El fichero ', C_FNAME, ' no se ha podido crear debido a: ', E.Message);
265+ end;
266+
267+ // Damos algo algo de información y esperamos la pulsación de una tecla.
268+ writeln('El fichero ', C_FNAME, ' se ha creado si todo ha ido bien. Presiona Enter para finalizar.');
269+ readln;
270+end.
271+```
272+
273+Se puede por tanto realizar la carga de un fichero completo a memoria siempre que su tamaño lógicamente sea menor que la cantidad de memoria disponible en el sistema. Para tamaños mayores de la memoria física disponible puede que el sistema operativo comience a utilizar la memoria páginada en el fichero de intercambio de memoria, haciendo esta función menos útil desde el punto de vista del rendimiento.
274+
275+``` pascal
276+program ReadBinaryDataInMemoryForAppend;
277+{$mode objfpc}
278+
279+uses
280+ Classes, Sysutils;
281+
282+const
283+ C_FNAME = 'binarydata.bin';
284+
285+var
286+ msApp: TMemoryStream;
287+
288+begin
289+ // Creamos el Stream de memoria para tener la corriente de datos.
290+ msApp := TMemoryStream.Create;
291+
292+ // Intercepción de errores en caso de que el fichero no pueda leerse o escribirse.
293+ try
294+ // Read the data into memory
295+ msApp.LoadFromFile(C_FNAME); // Cargamos el Stream en memoria con el contenido leido desde un fichero.
296+
297+ // Posicionamos con Seek al final del Stream de manera que podamos añadirle datos.
298+ msApp.Seek(0, soEnd); // donde 0 nos indica el offset o desplazamiento como primer parámetro, mientras que el segundo
299+ // parámetro nos indica a donde movernos, pudiendo ser soBeginning, soCurrent o bien soEnd, como en este caso.
300+
301+ // Escribe datos arbitrarios al Stream de memoria.
302+ msApp.WriteByte(68);
303+ msApp.WriteAnsiString('Algo de texto extra');
304+ msApp.WriteDWord(671202);
305+
306+ // Almacena de nuevo los datos a disco, sobreescribiendo el contenido previo.
307+ msApp.SaveToFile(C_FNAME);
308+
309+ // Maneja los errores.
310+ except
311+ on E:Exception do
312+ writeln('El fichero ', C_FNAME, ' no puede leerse o escribirse debido a: ', E.Message);
313+ end;
314+
315+ // Limpiamos, liberando la memoria que estaba utilizando el TMemoryStream.
316+ msApp.Free;
317+
318+ // Damos algo de información y esperamos por la pulsación de una tecla
319+ writeln('El fichero ', C_FNAME, ' fue extendido en contenido si todo fue bien. Presiona Enter para finalizar.');
320+ readln;
321+end.
322+```
323+
324+Con ficheros muy largos de varios GB, puede que sea necesario leer en buffers, digamos de 4096 bytes por ejemplo (se aconseja aquí que se utilicen múltiplos del tamaño de cluster del sistema de fichero) y realizamos algo con cada buffer de datos leidos.
325+
326+``` pascal
327+var
328+ TotalBytesRead, BytesRead : Int64;
329+ Buffer : array [0..4095] of byte; // o, array [0..4095] of char
330+ FileStream : TFileStream;
331+
332+try
333+ FileStream := TFileStream.Create;
334+ FileStream.Position := 0; // Nos aseguramos con esto que nos encontramos al comienzo del fichero.
335+ while TotalBytesRead <= FileStream.Size do // Mientras la cantidad de datos leidos es menor o igual que
336+ //el tamaño del Stream realizar:
337+ begin
338+ BytesRead := FileStream.Read(Buffer,sizeof(Buffer)); // Leer en bloques de datos de 4096 bytes
339+ inc(TotalBytesRead, BytesRead); // Incrementa TotalByteRead al tamaño del buffer, i.e. 4096 bytes
340+ // Realizar algo con el buffer de datos.
341+ end;
342+```
343+
344+### Copia de ficheros
345+
346+Con lo aprendido anteriormente podemos implementar una función simple de copia de ficheros (FreePascal no tiene ninguna en su RTL aunque Lazarus tiene [[CopyFile|copyfile]]) - se puede ajustar para ficheros de mayor tamaño, etc:
347+
348+``` pascal
349+program FileCopyDemo;
350+// Demonstración de la función FileCopy.
351+
352+{$mode objfpc}
353+
354+uses
355+ classes;
356+
357+const
358+ fSource = 'test.txt';
359+ fTarget = 'test.bak';
360+
361+function FileCopy(Source, Target: string): boolean;
362+// Copia fuente a destino, sobreescribe destino.
363+// Cachea el contenido completo en memoria.
364+// Retorna true si tiene exito; falso en caso contrario.
365+var
366+ MemBuffer: TMemoryStream;
367+begin
368+ result := false;
369+ MemBuffer := TMemoryStream.Create;
370+ try
371+ MemBuffer.LoadFromFile(Source);
372+ MemBuffer.SaveToFile(Target);
373+ result := true // Si no se genera un error la función nos devuelve true, caso contrario false.
374+ except
375+ end;
376+ // Clean up
377+ MemBuffer.Free // Libera el buffer en memoria para tener disponibles esos recursos.
378+end;
379+
380+begin
381+ If FileCopy(fSource, fTarget)
382+ then writeln('Fichero ', fSource, ' copiado a ', ftarget)
383+ else writeln('Fichero ', fSource, ' no copiado a ', ftarget);
384+ readln()
385+end.
386+```
387+
388+### Manejando ficheros de texto (TStringList)
389+
390+En general, se puede utilizar la clase TStringList para ficheros de texto, de forma que se cargue el fichero completo en memoria y tengamos un acceso sencillo a las líneas del mismo. Por supuesto, también se puede realizar la operación inversa para escribir el StringList al fichero:
391+
392+``` pascal
393+program StringListDemo;
394+{$mode objfpc}
395+
396+uses
397+ Classes, SysUtils;
398+
399+const
400+ C_FNAME = 'textfile.txt';
401+
402+var
403+ slInfo: TStringList;
404+
405+begin
406+ // Crea una instancia de StringList para manejar el fichero de texto.
407+ slInfo := TStringList.Create;
408+
409+ // Embebe el manejo del fichero en un bloque try/except para menejar los errores de manera elegante.
410+ try
411+ // Load the contents of the textfile completely in memory
412+ slInfo.LoadFromFile(C_FNAME);
413+
414+ // Añade más contenido.
415+ slInfo.Add('Una línea extra añadida al texto');
416+ slInfo.Add('Y otra más.');
417+ slInfo.Add('Paremos aquí.');
418+ slInfo.Add('La hora actual es ' + DateTimeToStr(now));
419+
420+ // Y escribimos el contenido de vuelta al disco, reeeplazando su contenido original.
421+ slInfo.SaveToFile(C_FNAME);
422+
423+ except
424+ // si se genera un error la razón la podemos encontrar aquí.
425+ on E: EInOutError do
426+ writeln('Error en el manejo de ficheros. Razón: ', E.Message);
427+ end;
428+
429+ // Limpiamos, liberando así memoria.
430+ slInfo.Free;
431+
432+ // Damos algo de información y esperamos a que se pulse una tecla.
433+ writeln('El fichero ', C_FNAME, ' se ha actualizado si todo salió correctamente. Presiona Enter para finalizar.');
434+ readln;
435+end.
436+```
437+
438+### Demostración: salvando una cadena simple a un fichero
439+
440+Para escribir una simple cadena a memoria puede que necesites el procedimiento definido a continuación. Fíjate que las cadenas (Strings) en FreePascal pueden ser extremadamente largas, esto es además útil para escribir un bloque grande o datos tipo texto a un fichero.
441+
442+``` pascal
443+program SaveStringToPathDemo;
444+{$mode objfpc}
445+
446+uses
447+ Classes, sysutils;
448+
449+const
450+ C_FNAME = 'textstringtofile.txt';
451+
452+// SaveStringToFile: función para almacenar una cadena de texto en un fichero en disco.
453+// Si el resultado de la función es igual a true, entonces significa que la cadena se escribió correctamente,
454+// en caso contrario significa que se ha encontrado un error.
455+
456+function SaveStringToFile(theString, filePath: AnsiString): boolean;
457+var
458+ fsOut: TFileStream;
459+begin
460+ // Por defecto asume que la escritura generará fallo.
461+ result := false;
462+
463+ // Escribe la cadena a un fichero, interceptando errores potenciales en el proceso.
464+ try
465+ fsOut := TFileStream.Create(filePath, fmCreate);
466+ fsOut.Write(theString[1], length(theString));
467+ fsOut.Free;
468+
469+ // En este punto sabemos que la escritura se realizó correctamente.
470+ result := true
471+
472+ except
473+ on E:Exception do
474+ writeln('La cadena no pudo escribirse. Detalles: ', E.ClassName, ': ', E.Message);
475+ end
476+end;
477+
478+//
479+// Programa principal.
480+//
481+begin
482+ // Trata de salvar un simple textstring a un fichero y da información si se realiza correctamente.
483+ if SaveStringToFile('>> este texto va a ser almacenado <<', C_FNAME) then
484+ writeln('Texto escrito correctametne a fichero')
485+ else
486+ writeln('Ha fallado la escritura del texto a fichero.');
487+
488+ // Espera a que el usuario presione la tecla Enter.
489+ readln
490+end.
491+```
492+
493+## Ver también
494+
495+- [[CopyFile]] Lazarus function (also available for command line programs) that copies files for you
496+- [[File]]
497+
498+---
499+*From the Free Pascal wiki, page "File Handling In Pascal/es", as it stood on 2020-02-14 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history