Difference between revisions of "File Handling In Pascal"

From Free Pascal wiki
Jump to navigationJump to search
m (Fixed syntax highlighting)
(25 intermediate revisions by 7 users not shown)
Line 2: Line 2:
  
 
==Overview==
 
==Overview==
Something every programmer needs to know is how to work with files. Files are used to persist data i.e. store data in such a way that it can be retrieved at a later moment, without having to recreate it. Files can be used to store user settings, error logs, measurement or calculation results, and more. This page explains the basics about file handling.
+
 
 +
Something every programmer needs to know is how to work with files. Files are used to persist data i.e. store data in such a way that it can be retrieved at a later time, without having to recreate it. Files can be used to store user settings, error logs, measurement or calculation results, and more. This page explains the basics if file handling.
  
 
==Old procedural style==
 
==Old procedural style==
When using files in classic (non-object oriented) Pascal, you can use a file of type TextFile (or simply Text) to store text, that is typically structured in lines. Every line ends with an end-of-line marker ([[End_of_Line|LineEnding]]). You can store strings in this type of file but also numbers that are formatted in different ways. These files can be opened and edited inside the Lazarus IDE or any other text editor.
 
  
For specific purposes you can create your own file type that can only store of one type of data. For example:
+
When using files in classic (non-object oriented) Pascal, you can use a file of type TextFile (or simply Text) to store text, that is typically structured in lines. Every line ends with an end-of-line marker ([[End_of_Line|LineEnding]]). You can store strings in this type of file, but also numbers which can be formatted in different ways. These files can be opened and edited inside the Lazarus IDE or any other text editor.
<syntaxhighlight>...
+
 
 +
For specific purposes you can create your own file type that can only store one type of data. For example:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
...
 
type
 
type
 
   TIntegerFile  = file of integer;  // Allows you to write only integer numbers to the file
 
   TIntegerFile  = file of integer;  // Allows you to write only integer numbers to the file
 
   TExtendedFile = file of extended; // Allows you to write only real numbers to the file
 
   TExtendedFile = file of extended; // Allows you to write only real numbers to the file
   TCharFile    = file of char;    // Allows you to write only single characters to the file </syntaxhighlight>
+
   TCharFile    = file of char;    // Allows you to write only single characters to the file  
 +
</syntaxhighlight>
  
 
===Input/Output error handling===
 
===Input/Output error handling===
 +
 
The [http://www.freepascal.org/docs-html/prog/progsu38.html#x45-440001.2.38 I/O error handling flag] tells the compiler how to deal with error situations: raise an exception or store the I/O result in the IOResult variable.
 
The [http://www.freepascal.org/docs-html/prog/progsu38.html#x45-440001.2.38 I/O error handling flag] tells the compiler how to deal with error situations: raise an exception or store the I/O result in the IOResult variable.
 
The I/O error handling flag is a compiler directive. To enable or disable it:
 
The I/O error handling flag is a compiler directive. To enable or disable it:
<syntaxhighlight>{$I+} // Errors will lead to an EInOutError exception (default)
+
 
 +
<syntaxhighlight lang=pascal>
 +
{$I+} // Errors will lead to an EInOutError exception (default)
 
{$I-} // Suppress I/O errors: check the IOResult variable for the error code
 
{$I-} // Suppress I/O errors: check the IOResult variable for the error code
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 24: Line 32:
  
 
===File procedures===
 
===File procedures===
These file handling procedures and functions are located in unit system. See the FPC documentation for more details: [http://www.freepascal.org/docs-html/rtl/system/index-5.html Reference for 'System' unit].
+
These file handling procedures and functions are located in [[System unit|unit System]]. See the FPC documentation for more details: [http://www.freepascal.org/docs-html/rtl/system/index-5.html Reference for 'System' unit].
  
 
* '''AssignFile''' (prevent the use of the older '''Assign''' procedure) - Assign a name to a file
 
* '''AssignFile''' (prevent the use of the older '''Assign''' procedure) - Assign a name to a file
Line 45: Line 53:
 
* '''SeekEOLn''' - Set file position to end of line
 
* '''SeekEOLn''' - Set file position to end of line
 
* '''Truncate''' - Truncate the file at position
 
* '''Truncate''' - Truncate the file at position
* '''Write''' - Write variable to a file
+
* '''[[Write]]''' - Write variable to a file
 
* '''WriteLn''' - Write variable to a text file and go to a new line
 
* '''WriteLn''' - Write variable to a text file and go to a new line
  
Line 51: Line 59:
 
A full example of handling a text file of type TextFile:
 
A full example of handling a text file of type TextFile:
  
<syntaxhighlight>program project1;
+
<syntaxhighlight lang=pascal>
 +
program CreateFile;
  
 
uses
 
uses
Line 85: Line 94:
 
   end;
 
   end;
  
   // Give feedback
+
   // Give feedback and wait for key press
   writeln('File ', C_FNAME, ' created. Press enter to stop.');
+
   writeln('File ', C_FNAME, ' created if all went ok. Press Enter to stop.');
 
   readln;
 
   readln;
 
end.</syntaxhighlight>
 
end.</syntaxhighlight>
Line 95: Line 104:
 
Note that exception handling was used as that is an easy way to perfom multiple file operations and handling the errors. You could also use {$I-}, but then you would have to check IOResult after each operation and modify your next operation.
 
Note that exception handling was used as that is an easy way to perfom multiple file operations and handling the errors. You could also use {$I-}, but then you would have to check IOResult after each operation and modify your next operation.
  
Here's how appending/adding to a file works:
+
Here's how appending more text to a textfile works:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
program EditFile;
+
program AppendToFile;
 
 
{$mode objfpc}
 
  
 
uses
 
uses
 
  Sysutils;
 
  Sysutils;
 +
 +
const
 +
  C_FNAME = 'textfile.txt';
  
 
var
 
var
File1: TextFile;
+
  tfOut: TextFile;
+
 
 
begin
 
begin
   WriteLn('Append file');
+
   // Set the name of the file that will receive some more text
   {$I+}
+
  AssignFile(tfOut, C_FNAME);
 +
 
 +
   // Embed the file handling in a try/except block to handle errors gracefully
 
   try
 
   try
     AssignFile(File1, 'Test.txt');
+
     // Open the file for appending, write some more text to it and close it.
     Append(File1);  
+
    append(tfOut);
     Writeln(File1,'adding some text...');
+
 
     CloseFile(File1);
+
     writeln(tfOut, 'Hello again textfile!');
 +
     writeln(tfOut, 'The result of 6 * 7 = ', 6 * 7);
 +
 
 +
     CloseFile(tfOut);
 +
 
 
   except
 
   except
 
     on E: EInOutError do
 
     on E: EInOutError do
    begin
+
     writeln('File handling error occurred. Details: ', E.Message);
     Writeln('File handling error occurred. Details: '+E.ClassName+'/'+E.Message);
 
    end;   
 
 
   end;
 
   end;
  WriteLn('Program finished. Press enter to stop.'); 
 
  Readln;
 
end.
 
</syntaxhighlight>
 
  
Reading a file:
+
  // Give feedback and wait for key press
 +
  writeln('File ', C_FNAME, ' might have more text. Press enter to stop.');
 +
  readln;
 +
end.</syntaxhighlight>
 +
 
 +
Reading a textfile:
  
<syntaxhighlight>program ReadFile;
+
<syntaxhighlight lang=pascal>
{$mode objfpc}
+
program ReadFile;
  
 
uses
 
uses
 
  Sysutils;
 
  Sysutils;
 +
 +
const
 +
  C_FNAME = 'textfile.txt';
  
 
var
 
var
File1: TextFile;
+
  tfIn: TextFile;
Str: String;
+
  s: string;
+
 
 
begin
 
begin
   Writeln('File Reading:');
+
   // Give some feedback
   AssignFile(File1, 'Test.txt');
+
  writeln('Reading the contents of file: ', C_FNAME);
   {$I+}
+
  writeln('=========================================');
 +
 
 +
  // Set the name of the file that will be read
 +
   AssignFile(tfIn, C_FNAME);
 +
 
 +
   // Embed the file handling in a try/except block to handle errors gracefully
 
   try
 
   try
     Reset(File1);
+
     // Open the file for reading
     repeat
+
    reset(tfIn);
       Readln(File1, Str); // Reads the whole line from the file
+
 
       Writeln(Str); // Writes the line read
+
    // Keep reading lines until the end of the file is reached
     until(EOF(File1)); // EOF(End Of File) The the program will keep reading new lines until there is none.
+
    while not eof(tfIn) do
     CloseFile(File1);
+
     begin
 +
       readln(tfIn, s);
 +
       writeln(s);
 +
     end;
 +
 
 +
    // Done so close the file
 +
     CloseFile(tfIn);
 +
 
 
   except
 
   except
 
     on E: EInOutError do
 
     on E: EInOutError do
    begin
+
     writeln('File handling error occurred. Details: ', E.Message);
     Writeln('File handling error occurred. Details: '+E.ClassName+'/'+E.Message);
 
    end;   
 
 
   end;
 
   end;
  WriteLn('Program finished. Press enter to stop.'); 
 
  Readln;
 
end.
 
</syntaxhighlight>
 
  
It is possible to do some file handling using chars instead of strings. This makes it look cool :D.
+
  // Wait for the user to end the program
 +
  writeln('=========================================');
 +
  writeln('File ', C_FNAME, ' was probably read. Press enter to stop.');
 +
  readln;
 +
end.</syntaxhighlight>
  
 
==Object style==
 
==Object style==
 +
In addition to the old style file handling routines mentioned above, a new system exists that uses the concept of streams (- of data) at a higher abstraction level. This means data can be read from or written to any location (disk, memory, hardware ports etc.) by one uniform interface.
 +
 +
In addition, most string handling classes have the ability to load and save content from/to a file. These methods are usually named SaveToFile and LoadFromFile. A lot of other objects (such as Lazarus grids) have similar functionality, including Lazarus datasets (DBExport). It pays to look through the documentation or source code before trying to roll your own save/load routines.
 +
 +
===Generic files of any type===
 +
For opening files for direct access TFileStream can be used. This class is an encapsulation of the system procedures FileOpen, FileCreate, FileRead, FileWrite, FileSeek and FileClose which resides in unit SysUtils.
 +
 +
[http://www.freepascal.org/docs-html/rtl/sysutils/ioroutines.html IO routines]
  
In addition to the old style file handling routines mentioned above, a new system exists that uses the concept of streams (streams of data) at a higher abstraction level, which means that you as a programmer have to perform fewer steps when dealing with files.
+
Here is a very simple example of appending one file to another using streams. It is equivalent to the append example above, but much simpler:
  
In addition, most string handling classes have the ability to load and save content from/to file. These methods are usually named SaveToFile and LoadFromFile. A lot of other objects (such as Lazarus grids) have similar functionality, including Lazarus datasets (DBExport); it pays to look through the documentation/source code before trying to roll your own import/export routines.
+
<syntaxhighlight lang=pascal>
 +
program inoutstream;
 +
uses
 +
  classes,sysutils;
 +
var
 +
Instream,OutStream:TFilestream;
 +
begin
 +
if (ParamCount = 2) and
 +
  FileExists(ParamStr(1)) and
 +
  FileExists(ParamStr(2)) then
 +
  begin
 +
  instream := TFilestream.Create(ParamStr(1), fmOpenRead);
 +
  try
 +
    outstream :=TFilestream.Create(Paramstr(2), fmOpenwrite);
 +
    try
 +
      outstream.position := Outstream.size;
 +
      outstream.copyfrom(instream,0);// appends
 +
    finally
 +
      instream.free;
 +
    end;
 +
  finally
 +
    outstream.free;
 +
  end;
 +
  end else writeln('use: inoutstream <infile> <outfile>');
 +
end.</syntaxhighlight><br>
 +
In the example below, note how we encapsulate the file handling action with a try..except block so that errors are handled correctly just as with the classic file handling routines (as not to convolute the example the fsOut.write is not put inside a try...finally block).  
  
===Binary files===
+
<syntaxhighlight lang=pascal>
 +
program WriteBinaryData;
 +
{$mode objfpc}
  
For opening files for direct access TFileStream should be used. This class is an encapsulation of the system procedures FileOpen, FileCreate, FileRead, FileWrite, FileSeek and FileClose which resides in unit SysUtils.
+
uses
 +
  Classes, Sysutils;
  
[http://www.freepascal.org/docs-html/rtl/sysutils/ioroutines.html IO routines]
+
const
 +
  C_FNAME = 'binarydata.bin';
  
In the example below, note how we encapsulate the file handling action with a try..finally block. This makes sure the Filestream object is always released (the finally... part) even if there are file access (or other) errors.
 
<syntaxhighlight>
 
 
var
 
var
   Buffer: array[0..1] of Char;
+
   fsOut    : TFileStream;
 +
  ChrBuffer: array[0..2] of char;
 +
 
 
begin
 
begin
   Buffer[0] := 'C';
+
   // Set up some random data that will get stored
   Buffer[1] := 'B';
+
  ChrBuffer[0] := 'A';
   with TFileStream.Create('SomeFile.bin', fmCreate) do
+
   ChrBuffer[1] := 'B';
 +
   ChrBuffer[2] := 'C';
 +
 
 +
  // Catch errors in case the file cannot be created
 
   try
 
   try
     Write(Buffer[0], SizeOf(Buffer)); //writes CB' to the file
+
     // Create the file stream instance, write to it and free it to prevent memory leaks
   finally
+
    fsOut := TFileStream.Create( C_FNAME, fmCreate);
     Free;
+
    fsOut.Write(ChrBuffer, sizeof(ChrBuffer));
 +
    fsOut.Free;
 +
 
 +
  // Handle errors
 +
   except
 +
     on E:Exception do
 +
      writeln('File ', C_FNAME, ' could not be created because: ', E.Message);
 
   end;
 
   end;
end;
+
 
</syntaxhighlight>
+
  // Give feedback and wait for key press
 +
  writeln('File ', C_FNAME, ' created if all went ok. Press Enter to stop.');
 +
  readln;
 +
end.</syntaxhighlight>
  
  
 
You can load entire files into memory too if its size is comparatively smaller than available system memory. Bigger sizes might work but your operating system will start using the page/swap file, making the exercise useless from a performance standpoint.
 
You can load entire files into memory too if its size is comparatively smaller than available system memory. Bigger sizes might work but your operating system will start using the page/swap file, making the exercise useless from a performance standpoint.
  
<syntaxhighlight>begin
+
<syntaxhighlight lang=pascal>
   with TMemoryStream.Create do
+
program ReadBinaryDataInMemoryForAppend;
 +
{$mode objfpc}
 +
 
 +
uses
 +
  Classes, Sysutils;
 +
 
 +
const
 +
  C_FNAME = 'binarydata.bin';
 +
 
 +
var
 +
  msApp: TMemoryStream;
 +
 
 +
begin
 +
   // Set up the stream
 +
  msApp := TMemoryStream.Create;
 +
 
 +
  // Catch errors in case the file cannot be read or written
 
   try
 
   try
     LoadFromFile('SomeFile.bin');
+
     // Read the data into memory
     Seek(0, soEnd);
+
    msApp.LoadFromFile(C_FNAME);
     WriteByte(Ord('A')); //Append the byte 65 (character 'A') to the end of the stream
+
 
     SaveToFile('SomeFile.bin');
+
     // Seek the end of the stream so data can be appended
   finally
+
    msApp.Seek(0, soEnd);
     Free;
+
 
 +
    // Write some arbitrary data to the memory stream
 +
     msApp.WriteByte(68);
 +
    msApp.WriteAnsiString('Some extra text');
 +
    msApp.WriteDWord(671202);
 +
 
 +
    // Store the data back on disk, overwriting the previous contents
 +
     msApp.SaveToFile(C_FNAME);
 +
 
 +
   // Handle errors
 +
  except
 +
     on E:Exception do
 +
      writeln('File ', C_FNAME, ' could not be read or written because: ', E.Message);
 
   end;
 
   end;
end;
+
 
 +
  // Clean up
 +
  msApp.Free;
 +
 
 +
  // Give feedback and wait for key press
 +
  writeln('File ', C_FNAME, ' was extended if all went ok. Press Enter to stop.');
 +
  readln;
 +
end.
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
With larger files of many Gb, you may want to read in buffers of, say, 4096 bytes (you're advised to use a multiple of the filesytem cluster or block size) and do something with the data of each buffer read.  
 
With larger files of many Gb, you may want to read in buffers of, say, 4096 bytes (you're advised to use a multiple of the filesytem cluster or block size) and do something with the data of each buffer read.  
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
var
 
var
 
   TotalBytesRead, BytesRead : Int64;
 
   TotalBytesRead, BytesRead : Int64;
Line 227: Line 339:
  
 
=== FileCopy ===
 
=== FileCopy ===
With the above, we can implement a simple FileCopy function (FreePascal has none in its RTL although Lazarus has [[copyfile|copyfile]]) - adjust as needed for bigger files etc:
+
With the above, we can implement a simple FileCopy function (FreePascal has none in its RTL although Lazarus has [[CopyFile|copyfile]]) - adjust as needed for bigger files etc:
<syntaxhighlight>
+
 
 +
<syntaxhighlight lang=pascal>
 
program FileCopyDemo;
 
program FileCopyDemo;
 
// Demonstration of FileCopy function
 
// Demonstration of FileCopy function
Line 244: Line 357:
 
// Copies source to target; overwrites target.
 
// Copies source to target; overwrites target.
 
// Caches entire file content in memory.
 
// Caches entire file content in memory.
// Returns true if succeeded; false if failed
+
// Returns true if succeeded; false if failed.
 
var
 
var
 
   MemBuffer: TMemoryStream;
 
   MemBuffer: TMemoryStream;
 
begin
 
begin
   result:=false;
+
   result := false;
   MemBuffer:=TMemoryStream.Create;
+
   MemBuffer := TMemoryStream.Create;
 
   try
 
   try
     try
+
     MemBuffer.LoadFromFile(Source);
      MemBuffer.LoadFromFile(Source);
+
    MemBuffer.SaveToFile(Target);  
      MemBuffer.Position:=0;
+
    result := true
      MemBuffer.SaveToFile(Target); //may be same as source
+
  except
      result:=true;
+
    //swallow exception; function result is false by default
    except
 
      result:=false; //swallow exception; convert to error code
 
    end;
 
  finally
 
    MemBuffer.Free;
 
 
   end;
 
   end;
 +
  // Clean up
 +
  MemBuffer.Free
 
end;
 
end;
  
 
begin
 
begin
 
   If FileCopy(fSource, fTarget)
 
   If FileCopy(fSource, fTarget)
  then Writeln('File ', fSource, ' copied to ', ftarget)
+
    then writeln('File ', fSource, ' copied to ', ftarget)
  else Writeln('File ', fSource, ' not copied to ', ftarget);
+
    else writeln('File ', fSource, ' not copied to ', ftarget);
   Readln();
+
   readln()
 
end.
 
end.
 
</syntaxhighlight>
 
</syntaxhighlight>
  
===Text files===
+
===Handling Text files (TStringList)===
In general, for text files you can use the TStringList class to load the entire file into memory and have simple access to their lines. Of course, you can also save your StringList back to file:
+
In general, for text files you can use the TStringList class to load the entire file into memory and have easy access to its lines. Of course, you can also write the StringList back to a file:
 
 
<syntaxhighlight>
 
program TStringListDemo;
 
// Stub to demonstrate TStringList class
 
  
 +
<syntaxhighlight lang=pascal>
 +
program StringListDemo;
 
{$mode objfpc}
 
{$mode objfpc}
  
 
uses
 
uses
Classes;
+
  Classes, SysUtils;
 +
 
 +
const
 +
  C_FNAME = 'textfile.txt';
 +
 
 +
var
 +
  slInfo: TStringList;
  
 
begin
 
begin
   with TStringList.Create do
+
   // Create an instance of the string list to handle the textfile
 +
  slInfo := TStringList.Create;
 +
 
 +
  // Embed the file handling in a try/except block to handle errors gracefully
 
   try
 
   try
     Add('Hello');
+
     // Load the contents of the textfile completely in memory
     SaveToFile('SomeFile.txt');
+
    slInfo.LoadFromFile(C_FNAME);
   finally
+
 
     Free;
+
    // Add some more contents
 +
    slInfo.Add('An extra line appended to the text');
 +
    slInfo.Add('And another one.');
 +
     slInfo.Add('Let''s stop here.');
 +
    slInfo.Add('It is now ' + DateTimeToStr(now));
 +
 
 +
    // And write the contents back to disk, replacing the original contents
 +
    slInfo.SaveToFile(C_FNAME);
 +
 
 +
   except
 +
    // If there was an error the reason can be found here
 +
     on E: EInOutError do
 +
      writeln('File handling error occurred. Reason: ', E.Message);
 
   end;
 
   end;
  Readln();
 
end.
 
</syntaxhighlight>
 
  
In order to write a single string to a stream you might want to use the following procedure:
+
  // Clean up
 +
  slInfo.Free;
  
<syntaxhighlight>
+
  // Give feedback and wait for key press
program SaveStringToPathDemo;
+
  writeln('File ', C_FNAME, ' updated if all went ok. Press Enter to stop.');
// Stub to demonstrate SaveStringToPath procedure
+
  readln;
 +
end.</syntaxhighlight>
 +
 
 +
===Demo: save single string to a file===
 +
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 lang=pascal>program SaveStringToPathDemo;
 
{$mode objfpc}
 
{$mode objfpc}
  
 
uses
 
uses
   classes;
+
   Classes, sysutils;
 +
 
 +
const
 +
  C_FNAME = 'textstringtofile.txt';
  
procedure SaveStringToPath(theString, filePath: String);
+
// SaveStringToFile: function to store a string of text into a diskfile.
// Write a single string to a file stream
+
//  If the function result equals true, the string was written ok.
 +
//  If not then there was some kind of error.
 +
function SaveStringToFile(theString, filePath: AnsiString): boolean;
 
var
 
var
   textFile: TFileStream;
+
   fsOut: TFileStream;
 
begin
 
begin
   textFile := TFileStream.Create(filePath, fmOpenWrite or fmCreate);
+
   // By default assume the writing will fail.
 +
  result := false;
 +
 
 +
  // Write the given string to a file, catching potential errors in the process.
 
   try
 
   try
     textFile.WriteBuffer(theString[1], Length(theString));
+
     fsOut := TFileStream.Create(filePath, fmCreate);
   finally
+
    fsOut.Write(theString[1], length(theString));
     textFile.Free;
+
    fsOut.Free;
   end;
+
 
 +
    // At his point it is known that the writing went ok.
 +
    result := true
 +
 
 +
   except
 +
     on E:Exception do
 +
      writeln('String could not be written. Details: ', E.ClassName, ': ', E.Message);
 +
   end
 
end;
 
end;
  
 +
//
 +
// Main program
 +
//
 +
begin
 +
  // Try to save a simple textstring in a file and give feedback of sucess.
 +
  if SaveStringToFile('>> this text gets stored <<', C_FNAME) then
 +
    writeln('Text succesfully written to a file')
 +
  else
 +
    writeln('Writing text to a file failed.');
 +
 +
  // Wait for the user to press Enter
 +
  readln
 +
end.
 +
</syntaxhighlight>
 +
 +
==File Handling in ISO Mode==
 +
 +
===Command Line Parameters===
 +
 +
The first option to assign external to internal files in iso mode is through command line parameters according to their order of appearance. Example:
 +
 +
<syntaxhighlight lang=pascal>
 +
program isotest1 (file1, file2);
 +
var
 +
  file1: text;
 +
  file2: file of integer;
 +
begin
 +
  reset(file1);
 +
  readln(file1);
 +
  rewrite(file2);
 +
  write(file2, 5);
 +
end.
 +
</syntaxhighlight>
 +
 +
The program is compiled with <code>fpc -Miso isotest1.pas</code> and run with <code>./isotest1 externa1l.txt external2.dat </code>.  files1 is assigned to external1.txt and file2 to external2.dat. Without command line parameters the files are assigned to stdin and stdout. As long as there is only one file for input and one for output, the same assignment can be achieved with this command: <code>./isotest1 <externa1l.txt >external2.dat </code>. file1 must exist prior to the run, file2 will be created.
 +
 +
===Transparent File Names===
 +
 +
The second option is transparent file names, achieved with the compiler option -Sr. Example:
 +
 +
<syntaxhighlight lang=pascal>
 +
program isotest2 (file1, file2);
 +
var
 +
  file1: text;
 +
  file2: text;
 
begin
 
begin
   SaveStringToPath('>>', 'SomeFile.txt');
+
   reset(file1);
 +
  readln(file1);
 +
  rewrite(file2);
 +
  write(file2, 5);
 
end.
 
end.
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
If the program is compiled with <code>fpc -Miso -Sr isotest2.pas</code> and run without command line parameters, the external files FILE1.txt and FILE2.txt are read and written. Note the uppercase conversion of the internal filenames. Files with different filenames can still be used through command line parameters.
  
 
==See also==
 
==See also==
* [[copyfile]] Lazarus function (also available for command line programs) that copies files for you
+
 
 +
* [[CopyFile]] Lazarus function (also available for command line programs) that copies files for you
 
* [[File]]
 
* [[File]]
 
+
* [[Howto Use TOpenDialog]]
[[Category:Tutorials]]
+
* [[Howto Use TSaveDialog]]
[[Category:Code]]
 

Revision as of 03:48, 15 February 2020

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

Overview

Something every programmer needs to know is how to work with files. Files are used to persist data i.e. store data in such a way that it can be retrieved at a later time, without having to recreate it. Files can be used to store user settings, error logs, measurement or calculation results, and more. This page explains the basics if file handling.

Old procedural style

When using files in classic (non-object oriented) Pascal, you can use a file of type TextFile (or simply Text) to store text, that is typically structured in lines. Every line ends with an end-of-line marker (LineEnding). You can store strings in this type of file, but also numbers which can be formatted in different ways. These files can be opened and edited inside the Lazarus IDE or any other text editor.

For specific purposes you can create your own file type that can only store one type of data. For example:

...
type
  TIntegerFile  = file of integer;  // Allows you to write only integer numbers to the file
  TExtendedFile = file of extended; // Allows you to write only real numbers to the file
  TCharFile     = file of char;     // Allows you to write only single characters to the file

Input/Output error handling

The I/O error handling flag tells the compiler how to deal with error situations: raise an exception or store the I/O result in the IOResult variable. The I/O error handling flag is a compiler directive. To enable or disable it:

{$I+} // Errors will lead to an EInOutError exception (default)
{$I-} // Suppress I/O errors: check the IOResult variable for the error code

By suppressing I/O errors ({$I-}) the file operation results go into the IOResult variable. This is a cardinal (number) type. Different numbers mean different errors. So you may want to check the documentation for the different errors[1].

File procedures

These file handling procedures and functions are located in unit System. See the FPC documentation for more details: Reference for 'System' unit.

  • AssignFile (prevent the use of the older Assign procedure) - Assign a name to a file
  • Append - Opens an existing file for appending data to end of file and editing it
  • BlockRead - Read data from an untyped file into memory
  • BlockWrite - Write data from memory to an untyped file
  • CloseFile (prevent the use of the older Close procedure) - Close opened file
  • EOF - Check for end of file
  • Erase - Erase file from disk
  • FilePos - Get position in file
  • FileSize - Get size of file
  • Flush - Write file buffers to disk
  • IOResult - Return result of last file IO operation
  • Read - Read from a text file
  • ReadLn - Read from a text file and go to the next line
  • Reset - Opens a file for reading
  • Rewrite - Create a file for writing
  • Seek - Change position in file
  • SeekEOF - Set file position to end of file
  • SeekEOLn - Set file position to end of line
  • Truncate - Truncate the file at position
  • Write - Write variable to a file
  • WriteLn - Write variable to a text file and go to a new line

Example

A full example of handling a text file of type TextFile:

program CreateFile;

uses
 Sysutils;

const
  C_FNAME = 'textfile.txt';

var
  tfOut: TextFile;

begin
  // Set the name of the file that will be created
  AssignFile(tfOut, C_FNAME);

  // Use exceptions to catch errors (this is the default so not absolutely requried)
  {$I+}

  // Embed the file creation in a try/except block to handle errors gracefully
  try
    // Create the file, write some text and close it.
    rewrite(tfOut);

    writeln(tfOut, 'Hello textfile!');
    writeln(tfOut, 'The answer to life, the universe and everything: ', 42);

    CloseFile(tfOut);

  except
    // If there was an error the reason can be found here
    on E: EInOutError do
      writeln('File handling error occurred. Details: ', E.ClassName, '/', E.Message);
  end;

  // Give feedback and wait for key press
  writeln('File ', C_FNAME, ' created if all went ok. Press Enter to stop.');
  readln;
end.

Now open the file in any text editor and you will see the above text written to it! You can test the error handling by running the program once, then set the file to read-only and run the program again.

Note that exception handling was used as that is an easy way to perfom multiple file operations and handling the errors. You could also use {$I-}, but then you would have to check IOResult after each operation and modify your next operation.

Here's how appending more text to a textfile works:

program AppendToFile;

uses
 Sysutils;

const
  C_FNAME = 'textfile.txt';

var
  tfOut: TextFile;

begin
  // Set the name of the file that will receive some more text
  AssignFile(tfOut, C_FNAME);

  // Embed the file handling in a try/except block to handle errors gracefully
  try
    // Open the file for appending, write some more text to it and close it.
    append(tfOut);

    writeln(tfOut, 'Hello again textfile!');
    writeln(tfOut, 'The result of 6 * 7 = ', 6 * 7);

    CloseFile(tfOut);

  except
    on E: EInOutError do
     writeln('File handling error occurred. Details: ', E.Message);
  end;

  // Give feedback and wait for key press
  writeln('File ', C_FNAME, ' might have more text. Press enter to stop.');
  readln;
end.

Reading a textfile:

program ReadFile;

uses
 Sysutils;

const
  C_FNAME = 'textfile.txt';

var
  tfIn: TextFile;
  s: string;

begin
  // Give some feedback
  writeln('Reading the contents of file: ', C_FNAME);
  writeln('=========================================');

  // Set the name of the file that will be read
  AssignFile(tfIn, C_FNAME);

  // Embed the file handling in a try/except block to handle errors gracefully
  try
    // Open the file for reading
    reset(tfIn);

    // Keep reading lines until the end of the file is reached
    while not eof(tfIn) do
    begin
      readln(tfIn, s);
      writeln(s);
    end;

    // Done so close the file
    CloseFile(tfIn);

  except
    on E: EInOutError do
     writeln('File handling error occurred. Details: ', E.Message);
  end;

  // Wait for the user to end the program
  writeln('=========================================');
  writeln('File ', C_FNAME, ' was probably read. Press enter to stop.');
  readln;
end.

Object style

In addition to the old style file handling routines mentioned above, a new system exists that uses the concept of streams (- of data) at a higher abstraction level. This means data can be read from or written to any location (disk, memory, hardware ports etc.) by one uniform interface.

In addition, most string handling classes have the ability to load and save content from/to a file. These methods are usually named SaveToFile and LoadFromFile. A lot of other objects (such as Lazarus grids) have similar functionality, including Lazarus datasets (DBExport). It pays to look through the documentation or source code before trying to roll your own save/load routines.

Generic files of any type

For opening files for direct access TFileStream can be used. This class is an encapsulation of the system procedures FileOpen, FileCreate, FileRead, FileWrite, FileSeek and FileClose which resides in unit SysUtils.

IO routines

Here is a very simple example of appending one file to another using streams. It is equivalent to the append example above, but much simpler:

program inoutstream;
uses
  classes,sysutils;
var
Instream,OutStream:TFilestream;
begin
if (ParamCount = 2) and
  FileExists(ParamStr(1)) and
  FileExists(ParamStr(2)) then
  begin
  instream := TFilestream.Create(ParamStr(1), fmOpenRead);
  try
    outstream :=TFilestream.Create(Paramstr(2), fmOpenwrite);
    try
      outstream.position := Outstream.size;
      outstream.copyfrom(instream,0);// appends
    finally
      instream.free;
    end;
  finally
    outstream.free;
  end;
  end else writeln('use: inoutstream <infile> <outfile>');
end.


In the example below, note how we encapsulate the file handling action with a try..except block so that errors are handled correctly just as with the classic file handling routines (as not to convolute the example the fsOut.write is not put inside a try...finally block).

program WriteBinaryData;
{$mode objfpc}

uses
  Classes, Sysutils;

const
  C_FNAME = 'binarydata.bin';

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

begin
  // Set up some random data that will get stored
  ChrBuffer[0] := 'A';
  ChrBuffer[1] := 'B';
  ChrBuffer[2] := 'C';

  // Catch errors in case the file cannot be created
  try
    // Create the file stream instance, write to it and free it to prevent memory leaks
    fsOut := TFileStream.Create( C_FNAME, fmCreate);
    fsOut.Write(ChrBuffer, sizeof(ChrBuffer));
    fsOut.Free;

  // Handle errors
  except
    on E:Exception do
      writeln('File ', C_FNAME, ' could not be created because: ', E.Message);
  end;

  // Give feedback and wait for key press
  writeln('File ', C_FNAME, ' created if all went ok. Press Enter to stop.');
  readln;
end.


You can load entire files into memory too if its size is comparatively smaller than available system memory. Bigger sizes might work but your operating system will start using the page/swap file, making the exercise useless from a performance standpoint.

program ReadBinaryDataInMemoryForAppend;
{$mode objfpc}

uses
  Classes, Sysutils;

const
  C_FNAME = 'binarydata.bin';

var
  msApp: TMemoryStream;

begin
  // Set up the stream
  msApp := TMemoryStream.Create;

  // Catch errors in case the file cannot be read or written
  try
    // Read the data into memory
    msApp.LoadFromFile(C_FNAME);

    // Seek the end of the stream so data can be appended
    msApp.Seek(0, soEnd);

    // Write some arbitrary data to the memory stream
    msApp.WriteByte(68);
    msApp.WriteAnsiString('Some extra text');
    msApp.WriteDWord(671202);

    // Store the data back on disk, overwriting the previous contents
    msApp.SaveToFile(C_FNAME);

  // Handle errors
  except
    on E:Exception do
      writeln('File ', C_FNAME, ' could not be read or written because: ', E.Message);
  end;

  // Clean up
  msApp.Free;

  // Give feedback and wait for key press
  writeln('File ', C_FNAME, ' was extended if all went ok. Press Enter to stop.');
  readln;
end.

With larger files of many Gb, you may want to read in buffers of, say, 4096 bytes (you're advised to use a multiple of the filesytem cluster or block size) and do something with the data of each buffer read.

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

try
  FileStream := TFileStream.Create;
  FileStream.Position := 0;  // Ensure you are at the start of the file
  while TotalBytesRead <= FileStream.Size do  // While the amount of data read is less than or equal to the size of the stream do
  begin
    BytesRead := FileStream.Read(Buffer,sizeof(Buffer));  // Read in 4096 of data
    inc(TotalBytesRead, BytesRead);                       // Increase TotalByteRead by the size of the buffer, i.e. 4096 bytes
    // Do something with Buffer data
  end;

FileCopy

With the above, we can implement a simple FileCopy function (FreePascal has none in its RTL although Lazarus has copyfile) - adjust as needed for bigger files etc:

program FileCopyDemo;
// Demonstration of FileCopy function

{$mode objfpc}

uses
  classes;

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

function FileCopy(Source, Target: string): boolean;
// Copies source to target; overwrites target.
// Caches entire file content in memory.
// Returns true if succeeded; false if failed.
var
  MemBuffer: TMemoryStream;
begin
  result := false;
  MemBuffer := TMemoryStream.Create;
  try
    MemBuffer.LoadFromFile(Source);
    MemBuffer.SaveToFile(Target); 
    result := true
  except
    //swallow exception; function result is false by default
  end;
  // Clean up
  MemBuffer.Free
end;

begin
  If FileCopy(fSource, fTarget)
    then writeln('File ', fSource, ' copied to ', ftarget)
    else writeln('File ', fSource, ' not copied to ', ftarget);
  readln()
end.

Handling Text files (TStringList)

In general, for text files you can use the TStringList class to load the entire file into memory and have easy access to its lines. Of course, you can also write the StringList back to a file:

program StringListDemo;
{$mode objfpc}

uses
  Classes, SysUtils;

const
  C_FNAME = 'textfile.txt';

var
  slInfo: TStringList;

begin
  // Create an instance of the string list to handle the textfile
  slInfo := TStringList.Create;

  // Embed the file handling in a try/except block to handle errors gracefully
  try
    // Load the contents of the textfile completely in memory
    slInfo.LoadFromFile(C_FNAME);

    // Add some more contents
    slInfo.Add('An extra line appended to the text');
    slInfo.Add('And another one.');
    slInfo.Add('Let''s stop here.');
    slInfo.Add('It is now ' + DateTimeToStr(now));

    // And write the contents back to disk, replacing the original contents
    slInfo.SaveToFile(C_FNAME);

  except
    // If there was an error the reason can be found here
    on E: EInOutError do
      writeln('File handling error occurred. Reason: ', E.Message);
  end;

  // Clean up
  slInfo.Free;

  // Give feedback and wait for key press
  writeln('File ', C_FNAME, ' updated if all went ok. Press Enter to stop.');
  readln;
end.

Demo: save single string to a file

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.

program SaveStringToPathDemo;
{$mode objfpc}

uses
  Classes, sysutils;

const
  C_FNAME = 'textstringtofile.txt';

// SaveStringToFile: function to store a string of text into a diskfile.
//   If the function result equals true, the string was written ok.
//   If not then there was some kind of error.
function SaveStringToFile(theString, filePath: AnsiString): boolean;
var
  fsOut: TFileStream;
begin
  // By default assume the writing will fail.
  result := false;

  // Write the given string to a file, catching potential errors in the process.
  try
    fsOut := TFileStream.Create(filePath, fmCreate);
    fsOut.Write(theString[1], length(theString));
    fsOut.Free;

    // At his point it is known that the writing went ok.
    result := true

  except
    on E:Exception do
      writeln('String could not be written. Details: ', E.ClassName, ': ', E.Message);
  end
end;

//
// Main program
//
begin
  // Try to save a simple textstring in a file and give feedback of sucess.
  if SaveStringToFile('>> this text gets stored <<', C_FNAME) then
    writeln('Text succesfully written to a file')
  else
    writeln('Writing text to a file failed.');

  // Wait for the user to press Enter
  readln
end.

File Handling in ISO Mode

Command Line Parameters

The first option to assign external to internal files in iso mode is through command line parameters according to their order of appearance. Example:

program isotest1 (file1, file2);
var
  file1: text;
  file2: file of integer;
begin
  reset(file1);
  readln(file1);
  rewrite(file2);
  write(file2, 5);
end.

The program is compiled with fpc -Miso isotest1.pas and run with ./isotest1 externa1l.txt external2.dat . files1 is assigned to external1.txt and file2 to external2.dat. Without command line parameters the files are assigned to stdin and stdout. As long as there is only one file for input and one for output, the same assignment can be achieved with this command: ./isotest1 <externa1l.txt >external2.dat . file1 must exist prior to the run, file2 will be created.

Transparent File Names

The second option is transparent file names, achieved with the compiler option -Sr. Example:

program isotest2 (file1, file2);
var
  file1: text;
  file2: text;
begin
  reset(file1);
  readln(file1);
  rewrite(file2);
  write(file2, 5);
end.

If the program is compiled with fpc -Miso -Sr isotest2.pas and run without command line parameters, the external files FILE1.txt and FILE2.txt are read and written. Note the uppercase conversion of the internal filenames. Files with different filenames can still be used through command line parameters.

See also