Sign in Join
Wiki copytemporary

File Handling In Pascal: what changed

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

Title: File Handling In Pascal

OldNewLine
1+## Overview
2+
3+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 of file handling.
4+
5+## Old procedural style
6+
7+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.
8+
9+For specific purposes you can create your own file type that can only store one type of data. For example:
10+
11+``` pascal
12+...
13+type
14+ TIntegerFile = file of integer; // Allows you to write only integer numbers to the file
15+ TExtendedFile = file of extended; // Allows you to write only real numbers to the file
16+ TCharFile = file of char; // Allows you to write only single characters to the file
17+```
18+
19+### Input/Output error handling
20+
21+The [I/O error handling flag](http://www.freepascal.org/docs-html/prog/progsu38.html#x45-440001.2.38) 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:
22+
23+``` pascal
24+{$I+} // Errors will lead to an EInOutError exception (default)
25+{$I-} // Suppress I/O errors: check the IOResult variable for the error code
26+```
27+
28+By suppressing I/O errors ({\$I-}) the file operation results go into the IOResult variable. This is a [[Cardinal|cardinal (number) type]]. Different numbers mean different errors. So you may want to check the documentation for the different errors[1](http://www.freepascal.org/docs-html/rtl/system/ioresult.html).
29+
30+### File procedures
31+
32+These file handling procedures and functions are located in [[System unit|unit System]]. See the FPC documentation for more details: [Reference for 'System' unit](http://www.freepascal.org/docs-html/rtl/system/index-5.html).
33+
34+- **AssignFile** (prevent the use of the older **[[Assign]]** procedure) - Assign a name to a file
35+- **Append** - Opens an existing file for appending data to end of file and editing it
36+- **BlockRead** - Read data from an untyped file into memory
37+- **BlockWrite** - Write data from memory to an untyped file
38+- **CloseFile** (prevent the use of the older **Close** procedure) - Close opened file
39+- **EOF** - Check for end of file
40+- **Erase** - Erase file from disk
41+- **FilePos** - Get position in file
42+- **FileSize** - Get size of file
43+- **Flush** - Write file buffers to disk
44+- **IOResult** - Return result of last file IO operation
45+- **Read** - Read from a text file
46+- **ReadLn** - Read from a text file and go to the next line
47+- **Reset** - Opens a file for reading
48+- **Rewrite** - Create a file for writing
49+- **Seek** - Change position in file
50+- **SeekEOF** - Set file position to end of file
51+- **SeekEOLn** - Set file position to end of line
52+- **Truncate** - Truncate the file at position
53+- **[[Write]]** - Write variable to a file
54+- **WriteLn** - Write variable to a text file and go to a new line
55+
56+### Example
57+
58+A full example of handling a text file of type TextFile:
59+
60+``` pascal
61+program CreateFile;
62+
63+{$mode objfpc}
64+
65+uses
66+ Sysutils;
67+
68+const
69+ C_FNAME = 'textfile.txt';
70+
71+var
72+ tfOut: TextFile;
73+
74+begin
75+ // Set the name of the file that will be created
76+ AssignFile(tfOut, C_FNAME);
77+
78+ // Use exceptions to catch errors (this is the default so not absolutely requried)
79+ {$I+}
80+
81+ // Embed the file creation in a try/except block to handle errors gracefully
82+ try
83+ // Create the file, write some text and close it.
84+ rewrite(tfOut);
85+
86+ writeln(tfOut, 'Hello textfile!');
87+ writeln(tfOut, 'The answer to life, the universe and everything: ', 42);
88+
89+ CloseFile(tfOut);
90+
91+ except
92+ // If there was an error the reason can be found here
93+ on E: EInOutError do
94+ writeln('File handling error occurred. Details: ', E.ClassName, '/', E.Message);
95+ end;
96+
97+ // Give feedback and wait for key press
98+ writeln('File ', C_FNAME, ' created if all went ok. Press Enter to stop.');
99+ readln;
100+end.
101+```
102+
103+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.
104+
105+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.
106+
107+Here's how appending more text to a textfile works:
108+
109+``` pascal
110+program AppendToFile;
111+
112+{$mode objfpc}
113+
114+uses
115+ Sysutils;
116+
117+const
118+ C_FNAME = 'textfile.txt';
119+
120+var
121+ tfOut: TextFile;
122+
123+begin
124+ // Set the name of the file that will receive some more text
125+ AssignFile(tfOut, C_FNAME);
126+
127+ // Embed the file handling in a try/except block to handle errors gracefully
128+ try
129+ // Open the file for appending, write some more text to it and close it.
130+ append(tfOut);
131+
132+ writeln(tfOut, 'Hello again textfile!');
133+ writeln(tfOut, 'The result of 6 * 7 = ', 6 * 7);
134+
135+ CloseFile(tfOut);
136+
137+ except
138+ on E: EInOutError do
139+ writeln('File handling error occurred. Details: ', E.Message);
140+ end;
141+
142+ // Give feedback and wait for key press
143+ writeln('File ', C_FNAME, ' might have more text. Press enter to stop.');
144+ readln;
145+end.
146+```
147+
148+Reading a textfile:
149+
150+``` pascal
151+program ReadFile;
152+
153+{$mode objfpc}
154+
155+uses
156+ Sysutils;
157+
158+const
159+ C_FNAME = 'textfile.txt';
160+
161+var
162+ tfIn: TextFile;
163+ s: string;
164+
165+begin
166+ // Give some feedback
167+ writeln('Reading the contents of file: ', C_FNAME);
168+ writeln('=========================================');
169+
170+ // Set the name of the file that will be read
171+ AssignFile(tfIn, C_FNAME);
172+
173+ // Embed the file handling in a try/except block to handle errors gracefully
174+ try
175+ // Open the file for reading
176+ reset(tfIn);
177+
178+ // Keep reading lines until the end of the file is reached
179+ while not eof(tfIn) do
180+ begin
181+ readln(tfIn, s);
182+ writeln(s);
183+ end;
184+
185+ // Done so close the file
186+ CloseFile(tfIn);
187+
188+ except
189+ on E: EInOutError do
190+ writeln('File handling error occurred. Details: ', E.Message);
191+ end;
192+
193+ // Wait for the user to end the program
194+ writeln('=========================================');
195+ writeln('File ', C_FNAME, ' was probably read. Press enter to stop.');
196+ readln;
197+end.
198+```
199+
200+## Object style
201+
202+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.
203+
204+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.
205+
206+### Generic files of any type
207+
208+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.
209+
210+[IO routines](http://www.freepascal.org/docs-html/rtl/sysutils/ioroutines.html)
211+
212+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:
213+
214+``` pascal
215+program InOutStream;
216+
217+{$mode objfpc}
218+
219+uses
220+ Classes, SysUtils;
221+var
222+ InStream, OutStream: TFileStream;
223+begin
224+ if (ParamCount = 2) and
225+ FileExists(ParamStr(1)) and
226+ FileExists(ParamStr(2)) then
227+ begin
228+ InStream := TFileStream.Create(ParamStr(1), fmOpenRead);
229+ try
230+ OutStream := TFileStream.Create(Paramstr(2), fmOpenWrite);
231+ try
232+ OutStream.Position := OutStream.size;
233+ OutStream.CopyFrom(InStream, 0); // appends
234+ finally
235+ OutStream.Free;
236+ end;
237+ finally
238+ InStream.Free;
239+ end;
240+ end else
241+ Writeln('Usage: inoutstream <infile> <outfile>');
242+end.
243+```
244+
245+\
246+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).
247+
248+``` pascal
249+program WriteBinaryData;
250+
251+{$mode objfpc}
252+
253+uses
254+ Classes, Sysutils;
255+
256+const
257+ C_FNAME = 'binarydata.bin';
258+
259+var
260+ fsOut : TFileStream;
261+ ChrBuffer: array[0..2] of char;
262+
263+begin
264+ // Set up some random data that will get stored
265+ ChrBuffer[0] := 'A';
266+ ChrBuffer[1] := 'B';
267+ ChrBuffer[2] := 'C';
268+
269+ // Catch errors in case the file cannot be created
270+ try
271+ // Create the file stream instance, write to it and free it to prevent memory leaks
272+ fsOut := TFileStream.Create( C_FNAME, fmCreate);
273+ fsOut.Write(ChrBuffer, sizeof(ChrBuffer));
274+ fsOut.Free;
275+
276+ // Handle errors
277+ except
278+ on E:Exception do
279+ writeln('File ', C_FNAME, ' could not be created because: ', E.Message);
280+ end;
281+
282+ // Give feedback and wait for key press
283+ writeln('File ', C_FNAME, ' created if all went ok. Press Enter to stop.');
284+ readln;
285+end.
286+```
287+
288+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.
289+
290+``` pascal
291+program ReadBinaryDataInMemoryForAppend;
292+
293+{$mode objfpc}
294+
295+uses
296+ Classes, Sysutils;
297+
298+const
299+ C_FNAME = 'binarydata.bin';
300+
301+var
302+ msApp: TMemoryStream;
303+
304+begin
305+ // Set up the stream
306+ msApp := TMemoryStream.Create;
307+
308+ // Catch errors in case the file cannot be read or written
309+ try
310+ // Read the data into memory
311+ msApp.LoadFromFile(C_FNAME);
312+
313+ // Seek the end of the stream so data can be appended
314+ msApp.Seek(0, soEnd);
315+
316+ // Write some arbitrary data to the memory stream
317+ msApp.WriteByte(68);
318+ msApp.WriteAnsiString('Some extra text');
319+ msApp.WriteDWord(671202);
320+
321+ // Store the data back on disk, overwriting the previous contents
322+ msApp.SaveToFile(C_FNAME);
323+
324+ // Handle errors
325+ except
326+ on E:Exception do
327+ writeln('File ', C_FNAME, ' could not be read or written because: ', E.Message);
328+ end;
329+
330+ // Clean up
331+ msApp.Free;
332+
333+ // Give feedback and wait for key press
334+ writeln('File ', C_FNAME, ' was extended if all went ok. Press Enter to stop.');
335+ readln;
336+end.
337+```
338+
339+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.
340+
341+``` pascal
342+var
343+ TotalBytesRead, BytesRead : Int64;
344+ Buffer : array [0..4095] of byte; // or, array [0..4095] of char
345+ FileStream : TFileStream;
346+
347+try
348+ FileStream := TFileStream.Create;
349+ FileStream.Position := 0; // Ensure you are at the start of the file
350+ TotalBytesRead := 0;
351+ while TotalBytesRead <= FileStream.Size do // While the amount of data read is less than or equal to the size of the stream do
352+ begin
353+ BytesRead := FileStream.Read(Buffer, SizeOf(Buffer)); // Read in 4096 of data
354+ Inc(TotalBytesRead, BytesRead); // Increase TotalByteRead by the size of the buffer, i.e. 4096 bytes
355+ // Do something with Buffer data
356+ end;
357+```
358+
359+### FileCopy
360+
361+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:
362+
363+``` pascal
364+program FileCopyDemo;
365+// Demonstration of FileCopy function
366+
367+{$mode objfpc}
368+
369+uses
370+ Classes;
371+
372+const
373+ fSource = 'test.txt';
374+ fTarget = 'test.bak';
375+
376+function FileCopy(Source, Target: string): boolean;
377+// Copies source to target; overwrites target.
378+// Caches entire file content in memory.
379+// Returns true if succeeded; false if failed.
380+var
381+ MemBuffer: TMemoryStream;
382+begin
383+ Result := false;
384+ MemBuffer := TMemoryStream.Create;
385+ try
386+ MemBuffer.LoadFromFile(Source);
387+ MemBuffer.SaveToFile(Target);
388+ Result := true
389+ except
390+ //swallow exception; function result is false by default
391+ end;
392+ // Clean up
393+ MemBuffer.Free
394+end;
395+
396+begin
397+ If FileCopy(fSource, fTarget)
398+ then Writeln('File ', fSource, ' copied to ', fTarget)
399+ else Writeln('File ', fSource, ' not copied to ', fTarget);
400+ Readln()
401+end.
402+```
403+
404+### Handling Text files (TStringList)
405+
406+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:
407+
408+``` pascal
409+program StringListDemo;
410+
411+{$mode objfpc}
412+
413+uses
414+ Classes, SysUtils;
415+
416+const
417+ C_FNAME = 'textfile.txt';
418+
419+var
420+ slInfo: TStringList;
421+
422+begin
423+ // Create an instance of the string list to handle the textfile
424+ slInfo := TStringList.Create;
425+
426+ // Embed the file handling in a try/except block to handle errors gracefully
427+ try
428+ // Load the contents of the textfile completely in memory
429+ slInfo.LoadFromFile(C_FNAME);
430+
431+ // Add some more contents
432+ slInfo.Add('An extra line appended to the text');
433+ slInfo.Add('And another one.');
434+ slInfo.Add('Let''s stop here.');
435+ slInfo.Add('It is now ' + DateTimeToStr(now));
436+
437+ // And write the contents back to disk, replacing the original contents
438+ slInfo.SaveToFile(C_FNAME);
439+
440+ except
441+ // If there was an error the reason can be found here
442+ on E: EInOutError do
443+ writeln('File handling error occurred. Reason: ', E.Message);
444+ end;
445+
446+ // Clean up
447+ slInfo.Free;
448+
449+ // Give feedback and wait for key press
450+ writeln('File ', C_FNAME, ' updated if all went ok. Press Enter to stop.');
451+ readln;
452+end.
453+```
454+
455+### Demo: save single string to a file
456+
457+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.
458+
459+``` pascal
460+program SaveStringToPathDemo;
461+
462+{$mode objfpc}
463+
464+uses
465+ Classes, sysutils;
466+
467+const
468+ C_FNAME = 'textstringtofile.txt';
469+
470+// SaveStringToFile: function to store a string of text into a diskfile.
471+// If the function result equals true, the string was written ok.
472+// If not then there was some kind of error.
473+function SaveStringToFile(theString, filePath: AnsiString): boolean;
474+var
475+ fsOut: TFileStream;
476+begin
477+ // By default assume the writing will fail.
478+ result := false;
479+
480+ // Write the given string to a file, catching potential errors in the process.
481+ try
482+ fsOut := TFileStream.Create(filePath, fmCreate);
483+ fsOut.Write(theString[1], length(theString));
484+ fsOut.Free;
485+
486+ // At his point it is known that the writing went ok.
487+ result := true
488+
489+ except
490+ on E:Exception do
491+ writeln('String could not be written. Details: ', E.ClassName, ': ', E.Message);
492+ end
493+end;
494+
495+//
496+// Main program
497+//
498+begin
499+ // Try to save a simple textstring in a file and give feedback of sucess.
500+ if SaveStringToFile('>> this text gets stored <<', C_FNAME) then
501+ writeln('Text succesfully written to a file')
502+ else
503+ writeln('Writing text to a file failed.');
504+
505+ // Wait for the user to press Enter
506+ readln
507+end.
508+```
509+
510+## File Handling in ISO Mode
511+
512+### Command Line Parameters
513+
514+The first option to assign external to internal files in iso mode is through command line parameters according to their order of appearance. Example:
515+
516+``` pascal
517+program isotest1 (file1, file2);
518+var
519+ file1: text;
520+ file2: file of integer;
521+begin
522+ reset(file1);
523+ readln(file1);
524+ rewrite(file2);
525+ write(file2, 5);
526+end.
527+```
528+
529+The program is compiled with `fpc -Miso isotest1.pas` and run with `./isotest1 external1.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 <external1.txt >external2.dat`. file1 must exist prior to the run, file2 will be created.
530+
531+### Transparent File Names
532+
533+The second option is transparent file names, achieved with the compiler option -Sr. Example:
534+
535+``` pascal
536+program isotest2 (file1, file2);
537+var
538+ file1: text;
539+ file2: text;
540+begin
541+ reset(file1);
542+ readln(file1);
543+ rewrite(file2);
544+ write(file2, 5);
545+end.
546+```
547+
548+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.
549+
550+## See also
551+
552+- [[CopyFile]] Lazarus function (also available for command line programs) that copies files for you
553+- [[File]]
554+- [[Howto Use TOpenDialog]]
555+- [[Howto Use TSaveDialog]]
556+
557+---
558+*From the Free Pascal wiki, page "File Handling In Pascal", as it stood on 2023-10-12 after a change by FPC user, 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