Difference between revisions of "File Handling In Pascal/es"

From Free Pascal wiki
Jump to navigationJump to search
m (Fixed syntax highlighting; deleted category included in page template)
 
(23 intermediate revisions by 2 users not shown)
Line 11: Line 11:
 
Para propósitos específicos se puede crear un tipo de fichero personalizado que puede almacenar únicamente un tipo de dato. Por ejemplo:
 
Para propósitos específicos se puede crear un tipo de fichero personalizado que puede almacenar únicamente un tipo de dato. Por ejemplo:
  
<syntaxhighlight>...
+
<syntaxhighlight lang=pascal>...
 
type
 
type
 
   TIntegerFile  = file of integer;  // Permite escribir únicamete números de tipo entero (integer) al fichero.
 
   TIntegerFile  = file of integer;  // Permite escribir únicamete números de tipo entero (integer) al fichero.
 
   TExtendedFile = file of extended; // Permite escribir úncamente números de tipo real al fichero.
 
   TExtendedFile = file of extended; // Permite escribir úncamente números de tipo real al fichero.
 
   TCharFile    = file of char;    // Permite escribir únicamente caracteres simples al fichero.
 
   TCharFile    = file of char;    // Permite escribir únicamente caracteres simples al fichero.
 +
  TPCharFile    = file of PChar;    // Permite escribir únicamente PChar al fichero.
 
   TByteFile    = file of byte;    // Permite escribir únicamente bytes al fichero.
 
   TByteFile    = file of byte;    // Permite escribir únicamente bytes al fichero.
 +
...
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 25: Line 27:
 
El flag de manejo del error de entrada/salida es una directiva del compilador. Para habilitarlo/deshabilitarlo escribimos:
 
El flag de manejo del error de entrada/salida es una directiva del compilador. Para habilitarlo/deshabilitarlo escribimos:
  
<syntaxhighlight>{$I+} // Los errores generarán una excepción EInOutError (por defecto)
+
<syntaxhighlight lang=pascal>
 +
{$I+} // Los errores generarán una excepción EInOutError (por defecto)
 
{$I-} // Suprime los errores de entrada/salida: chequea la veriable IOResult para saber su código de error.
 
{$I-} // Suprime los errores de entrada/salida: chequea la veriable IOResult para saber su código de error.
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 59: Line 62:
 
=== Ejemplo ===
 
=== Ejemplo ===
  
Un ejemplo completo de manejo de un fichero de texto del tipo 'TextFile':
+
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 }.
  
 
+
<syntaxhighlight lang=pascal>
<syntaxhighlight>program CreateFile;
+
program CreateFile;
  
 
uses
 
uses
Line 71: Line 74:
  
 
var
 
var
   tfOut: TextFile;
+
   tfOut: TextFile; // tipo fichero de salida.
  
 
begin
 
begin
Line 86: Line 89:
 
    
 
    
 
     //crea el fichero, escribe algo de texto y lo cierra.
 
     //crea el fichero, escribe algo de texto y lo cierra.
     rewrite(tfOut);
+
     ReWrite(tfOut);
 +
    WriteLn ('A continuación escribimos algo de texto al fichero ya que ponemos en WriteLn tfOut como salida');
 +
    WriteLn(tfOut, '¡Hola textfile!');
 +
    WriteLn(tfOut, 'Esto que se escribe directamente al fichero, no aparece por pantalla: ', 42);
  
     writeln(tfOut, 'Hello textfile!');
+
     CloseFile(tfOut); // Hemos terminado de escribir el texto en el fichero, por tanto le cerramos.
    writeln(tfOut, 'The answer to life, the universe and everything: ', 42);
 
  
     CloseFile(tfOut);
+
  except
 +
     // Si ocurre algún error podemos encontrar la razón en E: EInOutError
 +
      WriteLn('A continuación imprimimos en pantalla mediante E.ClassName / E.Message:');
 +
      WriteLn('Ha ocurrido un error en el manejo del fichero. Detalles: ', E.ClassName, '/', E.Message);
 +
      WriteLn ('Por otro lado podemos imprimir en pantalla el valor de la variable IOResult que es: ',IOResult);
  
  except
+
     // Tambien podemos ponerlo de la forma:
     // Si ocurre algún error podemos encontrar la razón en E: EInOutError  
+
    //  on E: EInOutError do
      writeln('Ha ocurrido un error en el manejo del fichero. Detalles: ', E.ClassName, '/', E.Message);
+
    //    begin
  end;
+
    //      Writeln('Ha ocurrido un error en el manejo del fichero. Detalle: '+E.ClassName,'/',E.Message);
 +
    //    end;
  
 
   // Da información y espera por la pulsación de una tecla.
 
   // Da información y espera por la pulsación de una tecla.
 
   writeln('Fichero ', C_FNAME, ' creado si todo fue bien. Presiona Enter para finalizar.');
 
   writeln('Fichero ', C_FNAME, ' creado si todo fue bien. Presiona Enter para finalizar.');
   readln;
+
   ReadLn;
 
end.</syntaxhighlight>
 
end.</syntaxhighlight>
  
 
Ahora abre el fichero generado utilizando para ello cualquier editor y verás justamente el texto que hemos escrito mediante código.
 
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.
+
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'''.
Now open the file in any text editor and you will see the above text written to it!
+
 
 +
Las constantes para los atributos de FileGetAttr y FileSetAttr son:
 +
 
 +
*faReadOnly: solo lectura.
 +
*faHidden: oculto (en Linux los encontramos en los ficheros que comienzan con un punto).
 +
*faSysFile: sistema.
 +
*faVolumeId: etiqueta de volumen.
 +
*faDirectory: directorio.
 +
*faArchive: archivo.
 +
 
 +
Adicionalmente es interesante echar un vistazo a la función [http://lazarus-ccr.sourceforge.net/docs/rtl/sysutils/fileopen.html FileOpen] donde lista los modos de fichero que acepta, así como otras funciones relacionadas, las cuales tienen su equivalencia a las listadas más arriba.
  
 
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.
 
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.
Line 111: Line 131:
 
Lo siguiente muestra como se puede añadir texto a un fichero del tipo textfile (al final del contenido ya existente):
 
Lo siguiente muestra como se puede añadir texto a un fichero del tipo textfile (al final del contenido ya existente):
  
<syntaxhighlight>program AppendToFile;
+
<syntaxhighlight lang=pascal>
 +
program AppendToFile;
  
 
uses
 
uses
Line 128: Line 149:
 
   // Embebe el manejo del fichero en un bloque try/except para gestionar los errores de manera elegante.
 
   // Embebe el manejo del fichero en un bloque try/except para gestionar los errores de manera elegante.
 
   try
 
   try
     // Abre el fichero en la modalidad añadir (appending), escrie algo de texto y lo cierra.
+
     // Abre el fichero en la modalidad añadir (appending), escribe algo de texto y lo cierra.
 
     append(tfOut);
 
     append(tfOut);
  
     writeln(tfOut, ' Hola de nuevo fichero de texto. ');
+
     WriteLn(tfOut, ' Hola de nuevo fichero de texto. ');
     writeln(tfOut, 'El resultado de 6 * 7 = ', 6 * 7);
+
     WriteLn(tfOut, 'El resultado de 6 * 7 = ', 6 * 7);
  
 
     CloseFile(tfOut);
 
     CloseFile(tfOut);
Line 142: Line 163:
  
 
   // Da información y espera a que se pulse una tecla.
 
   // Da información y espera a que se pulse una tecla.
   writeln('Fichero ', C_FNAME, ' puede contener más texto. Presiona Enter para finalizar.');
+
   WriteLn('Fichero ', C_FNAME, ' puede contener más texto. Presiona Enter para finalizar.');
   readln;
+
   ReadLn;
 
end.</syntaxhighlight>
 
end.</syntaxhighlight>
  
 
Leyendo un fichero de texto (textfile):
 
Leyendo un fichero de texto (textfile):
  
<syntaxhighlight>program ReadFile;
+
<syntaxhighlight lang=pascal>
 +
program ReadFile;
  
 
uses
 
uses
Line 162: Line 184:
 
begin
 
begin
 
   // Da algo de información
 
   // Da algo de información
   writeln('Leyendo el contenido del fichero: ', C_FNAME);
+
   WriteLn('Leyendo el contenido del fichero: ', C_FNAME);
   writeln('=========================================');
+
   WriteLn('=========================================');
  
 
   // Establece el nombre del fichero a leer.
 
   // Establece el nombre del fichero a leer.
 +
 
   AssignFile(tfIn, C_FNAME);
 
   AssignFile(tfIn, C_FNAME);
  
Line 171: Line 194:
 
  try
 
  try
 
     // Abre el fichero en la modalidad de lectura.
 
     // Abre el fichero en la modalidad de lectura.
     reset(tfIn);
+
     ReSet(tfIn);
  
 
     // Se mantiene leyendo líneas hasta alcanzar el final del fichero.
 
     // Se mantiene leyendo líneas hasta alcanzar el final del fichero.
 
     while not eof(tfIn) do  // Mientras no fin de fichero haz...
 
     while not eof(tfIn) do  // Mientras no fin de fichero haz...
 
     begin
 
     begin
       readln(tfIn, s); // Lee una línea de texto desde el fichero.
+
       ReadLn(tfIn, s); // Lee una línea de texto desde el fichero.
       writeln(s);  // Escribe la línea de texto leida anteriormente mostrándola en pantalla.
+
       WriteLn(s);  // Escribe la línea de texto leida anteriormente mostrándola en pantalla.
 
     end;
 
     end;
  
Line 185: Line 208:
 
   except
 
   except
 
     on E: EInOutError do
 
     on E: EInOutError do
    writeln('Ha ocurrido un error en el manejo del fichero. Detalles: ', E.Message);
+
    WriteLn('Ha ocurrido un error en el manejo del fichero. Detalles: ', E.Message);
 
   end;
 
   end;
  
 
   // Espera la intervención del usuario para finalizar el programa.
 
   // Espera la intervención del usuario para finalizar el programa.
   writeln('=========================================');
+
   WriteLn('=========================================');
   writeln('fichero ', C_FNAME, ' fue probablemente leido. Presiona Enter para finalizar.');
+
   WriteLn('fichero ', C_FNAME, ' fue probablemente leido. Presiona Enter para finalizar.');
   readln;
+
   ReadLn;
 
end.</syntaxhighlight>
 
end.</syntaxhighlight>
  
Line 198: Line 221:
 
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.
 
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.
  
Asociado a esto tenemos que la mayor parte de las clases manejadoras de cadenas tienen la habilidad de cargar y salvar contenidos a/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.
+
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.
  
 
=== Ficheros Binarios ===
 
=== Ficheros Binarios ===
  
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'''.
+
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'''.
  
 
[http://www.freepascal.org/docs-html/rtl/sysutils/ioroutines.html rutinas de entrada/salida (I/O)]
 
[http://www.freepascal.org/docs-html/rtl/sysutils/ioroutines.html rutinas de entrada/salida (I/O)]
Line 208: Line 231:
 
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).
 
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).
 
   
 
   
<syntaxhighlight>program WriteBinaryData;
+
<syntaxhighlight lang=pascal>
 +
program WriteBinaryData;
 
{$mode objfpc}
 
{$mode objfpc}
  
Line 247: Line 271:
 
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.
 
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.
  
<syntaxhighlight>program ReadBinaryDataInMemoryForAppend;
+
<syntaxhighlight lang=pascal>
 +
program ReadBinaryDataInMemoryForAppend;
 
{$mode objfpc}
 
{$mode objfpc}
  
Line 260: Line 285:
  
 
begin
 
begin
   // Set up the stream
+
   // Creamos el Stream de memoria para tener la corriente de datos.
 
   msApp := TMemoryStream.Create;
 
   msApp := TMemoryStream.Create;
  
   // Catch errors in case the file cannot be read or written
+
   // Intercepción de errores en caso de que el fichero no pueda leerse o escribirse.
 
   try
 
   try
 
     // Read the data into memory
 
     // Read the data into memory
     msApp.LoadFromFile(C_FNAME);
+
     msApp.LoadFromFile(C_FNAME); // Cargamos el Stream en memoria con el contenido leido desde un fichero.
  
     // Seek the end of the stream so data can be appended
+
     // Posicionamos con Seek al final del Stream de manera que podamos añadirle datos.
     msApp.Seek(0, soEnd);
+
     msApp.Seek(0, soEnd); // donde 0 nos indica el offset o desplazamiento como primer parámetro, mientras que el segundo
 +
    // parámetro nos indica a donde movernos, pudiendo ser soBeginning, soCurrent o bien soEnd, como en este caso.
  
     // Write some arbitrary data to the memory stream
+
     // Escribe datos arbitrarios al Stream de memoria.
 
     msApp.WriteByte(68);
 
     msApp.WriteByte(68);
     msApp.WriteAnsiString('Some extra text');
+
     msApp.WriteAnsiString('Algo de texto extra');
 
     msApp.WriteDWord(671202);
 
     msApp.WriteDWord(671202);
  
     // Store the data back on disk, overwriting the previous contents
+
     // Almacena de nuevo los datos a disco, sobreescribiendo el contenido previo.
 
     msApp.SaveToFile(C_FNAME);
 
     msApp.SaveToFile(C_FNAME);
  
   // Handle errors
+
   // Maneja los errores.
 
   except
 
   except
 
     on E:Exception do
 
     on E:Exception do
Line 296: Line 322:
 
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.
 
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.
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
var
 
var
 
   TotalBytesRead, BytesRead : Int64;
 
   TotalBytesRead, BytesRead : Int64;
Line 305: Line 331:
 
   FileStream := TFileStream.Create;
 
   FileStream := TFileStream.Create;
 
   FileStream.Position := 0;  // Nos aseguramos con esto que nos encontramos al comienzo del fichero.
 
   FileStream.Position := 0;  // Nos aseguramos con esto que nos encontramos al comienzo del fichero.
   while TotalBytesRead <= FileStream.Size do  // Mientras la cantidad de datos leidos es menor o igual que el tamaño del Stream realizar:
+
   while TotalBytesRead <= FileStream.Size do  // Mientras la cantidad de datos leidos es menor o igual que
 +
  //el tamaño del Stream realizar:
 
   begin
 
   begin
 
     BytesRead := FileStream.Read(Buffer,sizeof(Buffer));  // Leer en bloques de datos de 4096 bytes
 
     BytesRead := FileStream.Read(Buffer,sizeof(Buffer));  // Leer en bloques de datos de 4096 bytes
Line 315: Line 342:
 
=== Copia de ficheros ===
 
=== Copia de ficheros ===
  
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:
+
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:
<syntaxhighlight>
+
 
 +
<syntaxhighlight lang=pascal>
 
program FileCopyDemo;
 
program FileCopyDemo;
 
// Demonstración de la función FileCopy.
 
// Demonstración de la función FileCopy.
Line 332: Line 360:
 
// Copia fuente a destino, sobreescribe destino.
 
// Copia fuente a destino, sobreescribe destino.
 
// Cachea el contenido completo en memoria.
 
// Cachea el contenido completo en memoria.
// Retorna true si tiene exito; falso en caso contrario.urns true if succeeded; false if failed.
+
// Retorna true si tiene exito; falso en caso contrario.
 
var
 
var
 
   MemBuffer: TMemoryStream;
 
   MemBuffer: TMemoryStream;
Line 341: Line 369:
 
     MemBuffer.LoadFromFile(Source);
 
     MemBuffer.LoadFromFile(Source);
 
     MemBuffer.SaveToFile(Target);  
 
     MemBuffer.SaveToFile(Target);  
     result := true
+
     result := true // Si no se genera un error la función nos devuelve true, caso contrario false.
 
   except
 
   except
    //swallow exception; function result is false by default
 
 
   end;
 
   end;
 
   // Clean up
 
   // Clean up
   MemBuffer.Free
+
   MemBuffer.Free // Libera el buffer en memoria para tener disponibles esos recursos.
 
end;
 
end;
  
 
begin
 
begin
 
   If FileCopy(fSource, fTarget)
 
   If FileCopy(fSource, fTarget)
     then writeln('File ', fSource, ' copied to ', ftarget)
+
     then writeln('Fichero ', fSource, ' copiado a ', ftarget)
     else writeln('File ', fSource, ' not copied to ', ftarget);
+
     else writeln('Fichero ', fSource, ' no copiado a ', ftarget);
 
   readln()
 
   readln()
 
end.
 
end.
Line 361: Line 388:
 
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:
 
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:
  
<syntaxhighlight>program StringListDemo;
+
<syntaxhighlight lang=pascal>
 +
program StringListDemo;
 
{$mode objfpc}
 
{$mode objfpc}
  
Line 406: Line 434:
  
 
=== Demostración: salvando una cadena simple a un fichero ===
 
=== Demostración: salvando una cadena simple a un fichero ===
In order to write a single string to a stream you might want to use the procedure defined below. Note that strings in FreePascal can be extremely long, this is also a useful way to write a big block of textdata to a file.
+
 
<syntaxhighlight>program SaveStringToPathDemo;
+
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.
 +
 
 +
<syntaxhighlight lang=pascal>
 +
program SaveStringToPathDemo;
 
{$mode objfpc}
 
{$mode objfpc}
  
Line 416: Line 447:
 
   C_FNAME = 'textstringtofile.txt';
 
   C_FNAME = 'textstringtofile.txt';
  
// SaveStringToFile: function to store a string of text into a diskfile.
+
// SaveStringToFile: función para almacenar una cadena de texto en un fichero en disco.
//   If the function result equals true, the string was written ok.
+
// Si el resultado de la función es igual a true, entonces significa que la cadena se escribió correctamente,
//   If not then there was some kind of error.
+
// en caso contrario significa que se ha encontrado un error.
 +
 
 
function SaveStringToFile(theString, filePath: AnsiString): boolean;
 
function SaveStringToFile(theString, filePath: AnsiString): boolean;
 
var
 
var
 
   fsOut: TFileStream;
 
   fsOut: TFileStream;
 
begin
 
begin
   // By default assume the writing will fail.
+
   // Por defecto asume que la escritura generará fallo.
 
   result := false;
 
   result := false;
  
   // Write the given string to a file, catching potential errors in the process.
+
   // Escribe la cadena a un fichero, interceptando errores potenciales en el proceso.
 
   try
 
   try
 
     fsOut := TFileStream.Create(filePath, fmCreate);
 
     fsOut := TFileStream.Create(filePath, fmCreate);
Line 432: Line 464:
 
     fsOut.Free;
 
     fsOut.Free;
  
     // At his point it is known that the writing went ok.
+
     // En este punto sabemos que la escritura se realizó correctamente.
 
     result := true
 
     result := true
  
 
   except
 
   except
 
     on E:Exception do
 
     on E:Exception do
       writeln('String could not be written. Details: ', E.ClassName, ': ', E.Message);
+
       writeln('La cadena no pudo escribirse. Detalles: ', E.ClassName, ': ', E.Message);
 
   end
 
   end
 
end;
 
end;
  
 
//
 
//
// Main program
+
// Programa principal.
 
//
 
//
 
begin
 
begin
   // Try to save a simple textstring in a file and give feedback of sucess.
+
   // Trata de salvar un simple textstring a un fichero y da información si se realiza correctamente.
   if SaveStringToFile('>> this text gets stored <<', C_FNAME) then
+
   if SaveStringToFile('>> este texto va a ser almacenado <<', C_FNAME) then
     writeln('Text succesfully written to a file')
+
     writeln('Texto escrito correctametne a fichero')
 
   else
 
   else
     writeln('Writing text to a file failed.');
+
     writeln('Ha fallado la escritura del texto a fichero.');
  
   // Wait for the user to press Enter
+
   // Espera a que el usuario presione la tecla Enter.
 
   readln
 
   readln
 
end.
 
end.
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
== Ver también ==
 +
* [[CopyFile]] Lazarus function (also available for command line programs) that copies files for you
 +
* [[File]]

Latest revision as of 00:16, 15 February 2020

العربية (ar) English (en) español (es) suomi (fi) français (fr) 日本語 (ja) русский (ru) 中文(中国大陆)‎ (zh_CN) 中文(台灣)‎ (zh_TW)

Descripción

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.

Estilo del procedimiento antiguo

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 (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.

Para propósitos específicos se puede crear un tipo de fichero personalizado que puede almacenar únicamente un tipo de dato. Por ejemplo:

...
type
  TIntegerFile  = file of integer;  // Permite escribir únicamete números de tipo entero (integer) al fichero.
  TExtendedFile = file of extended; // Permite escribir úncamente números de tipo real al fichero.
  TCharFile     = file of char;     // Permite escribir únicamente caracteres simples al fichero.
  TPCharFile    = file of PChar;    // Permite escribir únicamente PChar al fichero.
  TByteFile     = file of byte;     // Permite escribir únicamente bytes al fichero.
...

Manejo de errores de Entrada/Salida

El I/O error handling flag 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.

El flag de manejo del error de entrada/salida es una directiva del compilador. Para habilitarlo/deshabilitarlo escribimos:

{$I+} // Los errores generarán una excepción EInOutError (por defecto)
{$I-} // Suprime los errores de entrada/salida: chequea la veriable IOResult para saber su código de error.

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 (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].

Procedimientos de fichero

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'.

  • AssignFile (previene del uso del antiguo procedimiento Assign ) - Asigna un nombre a un fichero.
  • Append - Abre un fichero ya existente en el modo añadir al final del mismo.
  • BlockRead - Lee un bloque de datos de un fichero sin tipo poniéndolo en memoria.
  • BlockWrite - Escribe un bloque de datos desde la memoria hacia un fichero sin tipo.
  • CloseFile (previene el uso del procedimiento antiguo Close ) - Cierra un fichero que está abierto.
  • 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.
  • Erase - Borra un fiechro del disco.
  • FilePos - Retorna da la posición en que nos encontramos dentro del fichero.
  • FileSize - Retorna el tamaño del fichero.
  • Flush - Escribe los buffers de fichero a disco.
  • IOResult - Retorna el resultado de la última operación de entrada/salida (I/O) de ficheros.
  • Read - Lee desde un fichero tipo texto.
  • ReadLn - Lee desde un fichero tipo texto (una línea entera) y salta a la siguiente línea.
  • Reset - Abre un fichero para lectura.
  • Rewrite - Crea un fichero para escritura.
  • Seek - Cambia a una posición dentro del fichero.
  • SeekEOF - Sitúa la posición dentro del fichero en su final.
  • SeekEOLn - sitúa la posición del fichero al final de la línea.
  • Truncate - Trunca el fichero en la posición indicada.
  • Write - Escribe una variable en el fichero.
  • WriteLn - Escribe una variable a un fichero de texto y salta a una nueva línea.

Ejemplo

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 }.

program CreateFile;

uses
 Sysutils;

const
  C_FNAME = 'textfile.txt';

var
  tfOut: TextFile; // tipo fichero de salida.

begin
  // Establece el nombre del fichero que vamos a crear
  AssignFile(tfOut, C_FNAME);

  // Habilitamos el uso de excepciones para interceptar los errores (esto es como está por defecto por lo tanto no es absolutamente requerido)

  {$I+}

  // Embebe la creación del fichero en un bloque try/except para manejar los errores elegántemente.
  
  try
  
    //crea el fichero, escribe algo de texto y lo cierra.
    ReWrite(tfOut);
    WriteLn ('A continuación escribimos algo de texto al fichero ya que ponemos en WriteLn tfOut como salida');
    WriteLn(tfOut, '¡Hola textfile!');
    WriteLn(tfOut, 'Esto que se escribe directamente al fichero, no aparece por pantalla: ', 42);

    CloseFile(tfOut); // Hemos terminado de escribir el texto en el fichero, por tanto le cerramos.

  except
    // Si ocurre algún error podemos encontrar la razón en E: EInOutError
      WriteLn('A continuación imprimimos en pantalla mediante E.ClassName / E.Message:'); 
      WriteLn('Ha ocurrido un error en el manejo del fichero. Detalles: ', E.ClassName, '/', E.Message);
      WriteLn ('Por otro lado podemos imprimir en pantalla el valor de la variable IOResult que es: ',IOResult);

    //  Tambien podemos ponerlo de la forma: 
    //  on E: EInOutError do
    //    begin
    //      Writeln('Ha ocurrido un error en el manejo del fichero. Detalle: '+E.ClassName,'/',E.Message);
    //    end;

  // Da información y espera por la pulsación de una tecla.
  writeln('Fichero ', C_FNAME, ' creado si todo fue bien. Presiona Enter para finalizar.');
  ReadLn;
end.

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.

Las constantes para los atributos de FileGetAttr y FileSetAttr son:

  • faReadOnly: solo lectura.
  • faHidden: oculto (en Linux los encontramos en los ficheros que comienzan con un punto).
  • faSysFile: sistema.
  • faVolumeId: etiqueta de volumen.
  • faDirectory: directorio.
  • faArchive: archivo.

Adicionalmente es interesante echar un vistazo a la función FileOpen donde lista los modos de fichero que acepta, así como otras funciones relacionadas, las cuales tienen su equivalencia a las listadas más arriba.

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.

Lo siguiente muestra como se puede añadir texto a un fichero del tipo textfile (al final del contenido ya existente):

program AppendToFile;

uses
 Sysutils;

const
  C_FNAME = 'textfile.txt';

var
  tfOut: TextFile;

begin
  // Establece el nombre del fichero que va a recibir más texto.
  AssignFile(tfOut, C_FNAME);

  // Embebe el manejo del fichero en un bloque try/except para gestionar los errores de manera elegante.
  try
    // Abre el fichero en la modalidad añadir (appending), escribe algo de texto y lo cierra.
    append(tfOut);

    WriteLn(tfOut, ' Hola de nuevo fichero de texto. ');
    WriteLn(tfOut, 'El resultado de 6 * 7 = ', 6 * 7);

    CloseFile(tfOut);

  except
    on E: EInOutError do
     writeln('Ha ocurrido un error en el manejo del fichero. Detalles: ', E.Message);
  end;

  // Da información y espera a que se pulse una tecla.
  WriteLn('Fichero ', C_FNAME, ' puede contener más texto. Presiona Enter para finalizar.');
  ReadLn;
end.

Leyendo un fichero de texto (textfile):

program ReadFile;

uses
 Sysutils;

const
  C_FNAME = 'textfile.txt';

var
  tfIn: TextFile;
  s: string;

begin
  // Da algo de información
  WriteLn('Leyendo el contenido del fichero: ', C_FNAME);
  WriteLn('=========================================');

  // Establece el nombre del fichero a leer.

  AssignFile(tfIn, C_FNAME);

  // Embebe el manejo del fichero en un bloque try/except para manejar errores de manera elegante.
 try
    // Abre el fichero en la modalidad de lectura.
    ReSet(tfIn);

    // Se mantiene leyendo líneas hasta alcanzar el final del fichero.
    while not eof(tfIn) do  // Mientras no fin de fichero haz...
    begin
      ReadLn(tfIn, s); // Lee una línea de texto desde el fichero.
      WriteLn(s);  // Escribe la línea de texto leida anteriormente mostrándola en pantalla.
    end;

    // Realizado, por tanto procedemos a cerrar el fichero.
    CloseFile(tfIn);

  except
    on E: EInOutError do
    WriteLn('Ha ocurrido un error en el manejo del fichero. Detalles: ', E.Message);
  end;

  // Espera la intervención del usuario para finalizar el programa.
  WriteLn('=========================================');
  WriteLn('fichero ', C_FNAME, ' fue probablemente leido. Presiona Enter para finalizar.');
  ReadLn;
end.

Estilo de Objetos

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.

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.

Ficheros Binarios

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.

rutinas de entrada/salida (I/O)

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).

program WriteBinaryData;
{$mode objfpc}

uses
  Classes, Sysutils;

const
  C_FNAME = 'binarydata.bin';

var
  fsOut    : TFileStream;
  ChrBuffer: array[0..2] of char;

begin
  // Establecemos algunos datos aleatorios para su almacenamiento.
  ChrBuffer[0] := 'A';
  ChrBuffer[1] := 'B';
  ChrBuffer[2] := 'C';

  // Interceptamos los errores en caso de que el fichero no pueda crearse.
  try
    // Creamos la instancia del flujo de datos,escribimos en el mismo y lo liberamos para evitar pérdidas de memoria.
    fsOut := TFileStream.Create( C_FNAME, fmCreate);
    fsOut.Write(ChrBuffer, sizeof(ChrBuffer));
    fsOut.Free;

  // Manejamos los errores
  except
    on E:Exception do
      writeln('El fichero ', C_FNAME, ' no se ha podido crear debido a: ', E.Message);
  end;

  // Damos algo algo de información y esperamos la pulsación de una tecla.
  writeln('El fichero ', C_FNAME, ' se ha creado si todo ha ido bien. Presiona Enter para finalizar.');
  readln;
end.

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.

program ReadBinaryDataInMemoryForAppend;
{$mode objfpc}

uses
  Classes, Sysutils;

const
  C_FNAME = 'binarydata.bin';

var
  msApp: TMemoryStream;

begin
  // Creamos el Stream de memoria para tener la corriente de datos.
  msApp := TMemoryStream.Create;

  // Intercepción de errores en caso de que el fichero no pueda leerse o escribirse.
  try
    // Read the data into memory
    msApp.LoadFromFile(C_FNAME); // Cargamos el Stream en memoria con el contenido leido desde un fichero.

    // Posicionamos con Seek al final del Stream de manera que podamos añadirle datos.
    msApp.Seek(0, soEnd); // donde 0 nos indica el offset o desplazamiento como primer parámetro, mientras que el segundo
    // parámetro nos indica a donde movernos, pudiendo ser soBeginning, soCurrent o bien soEnd, como en este caso.

    // Escribe datos arbitrarios al Stream de memoria.
    msApp.WriteByte(68);
    msApp.WriteAnsiString('Algo de texto extra');
    msApp.WriteDWord(671202);

    // Almacena de nuevo los datos a disco, sobreescribiendo el contenido previo.
    msApp.SaveToFile(C_FNAME);

  // Maneja los errores.
  except
    on E:Exception do
      writeln('El fichero ', C_FNAME, ' no puede leerse o escribirse debido a: ', E.Message);
  end;

  // Limpiamos, liberando la memoria que estaba utilizando el TMemoryStream.
  msApp.Free;

  // Damos algo de información y esperamos por la pulsación de una tecla
  writeln('El fichero ', C_FNAME, ' fue extendido en contenido si todo fue bien. Presiona Enter para finalizar.');
  readln;
end.

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.

var
  TotalBytesRead, BytesRead : Int64;
  Buffer : array [0..4095] of byte;  // o, array [0..4095] of char
  FileStream : TFileStream;

try
  FileStream := TFileStream.Create;
  FileStream.Position := 0;  // Nos aseguramos con esto que nos encontramos al comienzo del fichero.
  while TotalBytesRead <= FileStream.Size do  // Mientras la cantidad de datos leidos es menor o igual que
  //el tamaño del Stream realizar:
  begin
    BytesRead := FileStream.Read(Buffer,sizeof(Buffer));  // Leer en bloques de datos de 4096 bytes
    inc(TotalBytesRead, BytesRead);                       // Incrementa TotalByteRead al tamaño del buffer, i.e. 4096 bytes
    // Realizar algo con el buffer de datos.
  end;

Copia de ficheros

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) - se puede ajustar para ficheros de mayor tamaño, etc:

program FileCopyDemo;
// Demonstración de la función FileCopy.

{$mode objfpc}

uses
  classes;

const
  fSource = 'test.txt';
  fTarget = 'test.bak';

function FileCopy(Source, Target: string): boolean;
// Copia fuente a destino, sobreescribe destino.
// Cachea el contenido completo en memoria.
// Retorna true si tiene exito; falso en caso contrario.
var
  MemBuffer: TMemoryStream;
begin
  result := false;
  MemBuffer := TMemoryStream.Create;
  try
    MemBuffer.LoadFromFile(Source);
    MemBuffer.SaveToFile(Target); 
    result := true  // Si no se genera un error la función nos devuelve true, caso contrario false.
  except
  end;
  // Clean up
  MemBuffer.Free  // Libera el buffer en memoria para tener disponibles esos recursos.
end;

begin
  If FileCopy(fSource, fTarget)
    then writeln('Fichero ', fSource, ' copiado a ', ftarget)
    else writeln('Fichero ', fSource, ' no copiado a ', ftarget);
  readln()
end.

Manejando ficheros de texto (TStringList)

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:

program StringListDemo;
{$mode objfpc}

uses
  Classes, SysUtils;

const
  C_FNAME = 'textfile.txt';

var
  slInfo: TStringList;

begin
  // Crea una instancia de StringList para manejar el fichero de texto.
  slInfo := TStringList.Create;

  // Embebe el manejo del fichero en un bloque try/except para menejar los errores de manera elegante.
  try
    // Load the contents of the textfile completely in memory
    slInfo.LoadFromFile(C_FNAME);

    // Añade más contenido.
    slInfo.Add('Una línea extra añadida al texto');
    slInfo.Add('Y otra más.');
    slInfo.Add('Paremos aquí.');
    slInfo.Add('La hora actual es ' + DateTimeToStr(now));

    // Y escribimos el contenido de vuelta al disco, reeeplazando su contenido original.
    slInfo.SaveToFile(C_FNAME);

  except
    // si se genera un error la razón la podemos encontrar aquí.
    on E: EInOutError do
      writeln('Error en el manejo de ficheros. Razón: ', E.Message);
  end;

  // Limpiamos, liberando así memoria.
  slInfo.Free;

  // Damos algo de información y esperamos a que se pulse una tecla.
  writeln('El fichero ', C_FNAME, ' se ha actualizado si todo salió correctamente. Presiona Enter para finalizar.');
  readln;
end.

Demostración: salvando una cadena simple a un fichero

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.

program SaveStringToPathDemo;
{$mode objfpc}

uses
  Classes, sysutils;

const
  C_FNAME = 'textstringtofile.txt';

// SaveStringToFile: función para almacenar una cadena de texto en un fichero en disco.
// Si el resultado de la función es igual a true, entonces significa que la cadena se escribió correctamente,
// en caso contrario significa que se ha encontrado un error.

function SaveStringToFile(theString, filePath: AnsiString): boolean;
var
  fsOut: TFileStream;
begin
  // Por defecto asume que la escritura generará fallo.
  result := false;

  // Escribe la cadena a un fichero, interceptando errores potenciales en el proceso.
  try
    fsOut := TFileStream.Create(filePath, fmCreate);
    fsOut.Write(theString[1], length(theString));
    fsOut.Free;

    // En este punto sabemos que la escritura se realizó correctamente.
    result := true

  except
    on E:Exception do
      writeln('La cadena no pudo escribirse. Detalles: ', E.ClassName, ': ', E.Message);
  end
end;

//
// Programa principal.
//
begin
  // Trata de salvar un simple textstring a un fichero y da información si se realiza correctamente.
  if SaveStringToFile('>> este texto va a ser almacenado <<', C_FNAME) then
    writeln('Texto escrito correctametne a fichero')
  else
    writeln('Ha fallado la escritura del texto a fichero.');

  // Espera a que el usuario presione la tecla Enter.
  readln
end.

Ver también

  • CopyFile Lazarus function (also available for command line programs) that copies files for you
  • File