Text Files Grade 11
Text files allow your Delphi program to save and load data that persists between runs. Learn the four operations: reading, writing, appending, and checking existence.
What Is a Text File?
A text file is a file that stores plain, readable characters with no special formatting — just letters, numbers and line breaks, exactly as you would type them into Notepad. There are no fonts, colours, or hidden layout codes like you would find in a Word document; it is simply text, one line after another.
Think of a text file as a plain notepad. You can open it, read what is written, add a new line at the bottom, or tear out the page and start fresh. There is nothing fancy inside — just the words you wrote.
Why Do We Use Files?
The variables in your program only live in memory (RAM) while the program is running. The moment you close the program, everything in those variables is gone. A file gives your data persistence — it is saved onto the disk so that it survives after the program closes and is still there the next time you run it.
Example: a marks program that lets a teacher capture results is useless if every mark vanishes when she closes it. By writing the marks to a text file, the data is safely stored and can be read back tomorrow.
Physical vs Logical Filename
When working with files in Delphi there are two "names" to keep straight:
- Physical filename – the real name of the file as it exists on the disk, for example
'Data.txt'. This is what you would see in File Explorer. - Logical filename – the file variable inside your program (of type
TextFile), for exampleF. Your code talks to this variable, not directly to the disk.
The AssignFile command is what links the two together — it tells Delphi "whenever I use the logical variable F, I really mean the physical file Data.txt on the disk".
Think of a postbox. The physical filename is the actual postbox standing in the street with a real street address. The logical filename is the label F you stick on your desk that says "this drawer represents that postbox". AssignFile is the act of writing the street address onto your label so the two are connected.
What Each File Command Does
Before looking at the table, here is each command explained in plain words:
- AssignFile(F, 'file.txt') – links your file variable to the real file on disk. Always the very first step.
- Reset(F) – opens an existing file so you can read from it, starting at the very beginning.
- Rewrite(F) – creates a brand-new empty file for writing. If the file already exists, its old contents are wiped out, so use it with care.
- Append(F) – opens an existing file and moves to the very end, so new lines are added without destroying what is already there.
- ReadLn(F, S) – reads one whole line from the file into the variable
S, then moves on to the next line. - WriteLn(F, S) – writes the contents of
Sinto the file as one line. - CloseFile(F) – saves and properly closes the file. Like shutting and locking the postbox so nothing is lost.
- EOF(F) – short for "End Of File". Returns
Trueonce you have read the last line, so your loop knows when to stop.
Key Procedures and Functions
| Command | Purpose |
|---|---|
AssignFile(F, 'file.txt') | Links the file variable F to a file on disk |
Reset(F) | Opens existing file for reading (from beginning) |
Rewrite(F) | Creates/overwrites a file for writing |
Append(F) | Opens existing file for writing — adds to end |
CloseFile(F) | Saves and closes the file |
ReadLn(F, S) | Reads one line from file into variable S |
WriteLn(F, S) | Writes variable S as one line to the file |
EOF(F) | Returns True when the end of the file is reached |
FileExists('file.txt') | Returns True if the file exists on disk |
Reading a Text File
var
F: TextFile;
S: string;
begin
if FileExists('Data.txt') then
begin
AssignFile(F, 'Data.txt');
Reset(F); // open for reading
while not EOF(F) do
begin
ReadLn(F, S);
memOut.Lines.Add(S); // process each line
end;
CloseFile(F);
end
else
ShowMessage('File not found!');
end;Quick Alternative — LoadFromFile and SaveToFile
A Memo or RichEdit's Lines property has two built-in methods that skip the whole AssignFile/Reset/ReadLn loop when you just want to dump a text file straight onto the screen (or the screen straight into a file). One line does the job of the entire loop above.
memOutput.Lines.Clear;
memOutput.Lines.LoadFromFile('Data.txt'); // reads the whole file into the Memo
// ... later, to save the Memo's content back to a file:
memOutput.Lines.SaveToFile('Output.txt'); // writes every line in the Memo to a fileLoadFromFile/SaveToFile are fast and simple, but they give you no control over each line — you cannot validate, split or calculate anything while the file loads. Use them only when you want to display or save a file as-is. As soon as a question asks you to process the data (split fields, calculate a total, search for a value), you need the AssignFile/Reset/ReadLn/EOF loop instead.
Checking a File Exists with try..except
Using FileExists is the simplest way to avoid a crash. A second method is to try to open the file and catch the error if it is missing. A try..except block runs the risky code in the try part; if an exception happens (for example the file does not exist, so Reset fails), control jumps straight to the except part instead of crashing the program.
var
F: TextFile;
S: string;
begin
AssignFile(F, 'Data.txt');
try
Reset(F); // raises an error if the file is missing
while not EOF(F) do
begin
ReadLn(F, S);
memOut.Lines.Add(S);
end;
CloseFile(F);
except
on E: EInOutError do
ShowMessage('Could not open the file: ' + E.Message);
end;
end;FileExists checks before opening and lets you avoid the problem; try..except reacts after an error occurs. try..except also catches other I/O problems (e.g. a corrupt or locked file), so the two are often combined: test with FileExists, then wrap the file operations in try..except as a safety net. The exception type for file errors is EInOutError.
Writing to a Text File (Overwrite)
var
F: TextFile;
begin
AssignFile(F, 'Output.txt');
Rewrite(F); // creates or overwrites
WriteLn(F, sName + ',' + sSurname + ',' + IntToStr(iAge));
CloseFile(F);
end;Appending to a Text File
var
F: TextFile;
begin
AssignFile(F, 'Data.txt');
Append(F); // adds to existing file
WriteLn(F, 'New data line');
CloseFile(F);
end;Reading CSV Data into Arrays
var
F: TextFile;
sLine, sName: string;
iMark, iCount, iPos: Integer;
begin
iCount := 0;
AssignFile(F, 'marks.txt');
Reset(F);
while not EOF(F) do
begin
ReadLn(F, sLine);
iPos := Pos(',', sLine);
Inc(iCount);
arrNames[iCount] := Copy(sLine, 1, iPos - 1);
arrMarks[iCount] := StrToInt(Copy(sLine, iPos + 1, Length(sLine)));
end;
CloseFile(F);
end;Forgetting CloseFile(F) means the file remains locked and data may not be saved properly.
Worked Example — Putting It All Together
Exam questions rarely test one skill on its own. A typical Paper 1 question gives you a text file, asks you to extract and manipulate each line, store the parts in parallel arrays, and then process them. Here is that whole pattern in one example.
A text file marks.txt stores one learner per line as Surname,Name,Mark:
Coetzee,Mari,72
Naidoo,Priya,88
Botha,Jan,64
Khumalo,Sipho,91Task: read the file into parallel arrays, then (a) display each learner as M. Coetzee: 72%, (b) show the class average, and (c) find the top learner.
Step 0 — Declare the parallel arrays
const
Max = 100; // most learners the arrays can hold
var
arrSurname : array[1..MAX] of string;
arrName : array[1..MAX] of string;
arrMark : array[1..MAX] of integer;
iCount : integer; // how many learners were actually readThe three arrays are parallel: index [2] in each one belongs to the same learner (Priya Naidoo, 88).
Step 1 — Read the file & split each line
Read one line at a time. Use Pos to find each comma, then Copy and Delete to chop the line into its three fields.
var
F : TextFile;
sLine : string;
iPos : integer;
begin
iCount := 0;
if not FileExists('marks.txt') then
begin
ShowMessage('File not found!');
Exit;
end;
AssignFile(F, 'marks.txt');
Reset(F);
while not EOF(F) do
begin
ReadLn(F, sLine); // e.g. 'Coetzee,Mari,72'
Inc(iCount);
// field 1 - surname (up to the first comma)
iPos := Pos(',', sLine);
arrSurname[iCount] := Copy(sLine, 1, iPos - 1); // 'Coetzee'
Delete(sLine, 1, iPos); // -> 'Mari,72'
// field 2 - first name (up to the next comma)
iPos := Pos(',', sLine);
arrName[iCount] := Copy(sLine, 1, iPos - 1); // 'Mari'
Delete(sLine, 1, iPos); // -> '72'
// field 3 - whatever remains is the mark
arrMark[iCount] := StrToInt(sLine); // 72
end;
CloseFile(F);
end;Step 2 — Manipulate the strings for display
Build the initial from the first letter of the first name with Copy(name, 1, 1).
var
i : integer;
sInitial : string;
begin
for i := 1 to iCount do
begin
sInitial := Copy(arrName[i], 1, 1); // 'M'
memOut.Lines.Add(sInitial + '. ' + arrSurname[i]
+ ': ' + IntToStr(arrMark[i]) + '%');
end;
end;Output: M. Coetzee: 72%, P. Naidoo: 88%, J. Botha: 64%, S. Khumalo: 91%
Step 3 — Class average
(Assume iTotal : integer and rAverage : real are declared.)
iTotal := 0;
for i := 1 to iCount do
iTotal := iTotal + arrMark[i];
rAverage := iTotal / iCount;
ShowMessage('Class average: ' +
FloatToStrF(rAverage, ffFixed, 4, 1) + '%');Step 4 — Find the top learner
Search arrMark for the highest value, remembering its index. Because the arrays are parallel, that same index gives you the winner's name and surname too.
iTopPos := 1;
for i := 2 to iCount do
if arrMark[i] > arrMark[iTopPos] then
iTopPos := i;
ShowMessage('Top learner: ' + arrName[iTopPos] + ' ' + arrSurname[iTopPos]
+ ' (' + IntToStr(arrMark[iTopPos]) + '%)');In a single question you have used text file handling (FileExists, Reset, ReadLn, EOF, CloseFile), string manipulation (Pos, Copy, Delete), parallel arrays (three arrays sharing one index), and processing (an aggregate and finding a maximum). Remember the dinner-set rule: the winning index carries the name, surname and mark together.
And back out — writing the results to a new file
Often the last step is to write your processed results to a new text file. Use Rewrite to create it, WriteLn to add each line, and CloseFile to save. Here we write a pass/fail report from the arrays built above.
var
Fout : TextFile;
i : integer;
sResult : string;
begin
AssignFile(Fout, 'report.txt');
Rewrite(Fout); // creates (or overwrites) the file
for i := 1 to iCount do
begin
if arrMark[i] >= 50 then
sResult := 'PASS'
else
sResult := 'FAIL';
WriteLn(Fout, arrSurname[i] + ',' + IntToStr(arrMark[i]) + ',' + sResult);
end;
CloseFile(Fout); // saves and closes the file
end;That completes the full read → process → write cycle: read the data in, work with it in arrays, then save the results back out to a new file.
Working with More Than One Text File at a Time
A practical question will sometimes ask you to read from one existing file and, in the same pass, sort its lines into two or more new files depending on a condition. Reusing the marks.txt file from the worked example above, here is a program that reads it once and writes each learner's surname into either Pass.txt or Fail.txt depending on their mark. The file being read only ever needs Reset; every file being created or overwritten needs its own Rewrite call, and all three files stay open together inside a single while not EOF loop.
var
Fin, FPass, FFail : TextFile;
sLine, sSurname : string;
iPos, iMark : integer;
begin
AssignFile(Fin, 'marks.txt');
AssignFile(FPass, 'Pass.txt');
AssignFile(FFail, 'Fail.txt');
try
Reset(Fin); // only the file being read needs a try..except
except
ShowMessage('marks.txt could not be opened');
Exit;
end;
Rewrite(FPass); // creates/overwrites the two output files
Rewrite(FFail);
while not EOF(Fin) do
begin
ReadLn(Fin, sLine); // e.g. 'Coetzee,Mari,72'
iPos := Pos(',', sLine);
sSurname := Copy(sLine, 1, iPos - 1);
Delete(sLine, 1, iPos);
iPos := Pos(',', sLine);
Delete(sLine, 1, iPos); // discard the first name, keep the mark
iMark := StrToInt(sLine);
if iMark >= 50 then
WriteLn(FPass, sSurname)
else
WriteLn(FFail, sSurname);
end;
CloseFile(Fin);
CloseFile(FPass);
CloseFile(FFail);
end;If a file with that name already exists, Rewrite does not simply open it — it empties it first. So if Pass.txt already held data from a previous run, that data is gone the instant Rewrite(FPass) executes. Point Rewrite only at the files you genuinely intend to (re)create; call it on the wrong variable by mistake and there is no way to get the lost data back. The file you are reading from should only ever be opened with Reset.
A physical filename does not have to be a fixed string — it can be assembled from user input or a variable, which is useful when every record needs its own file. If the user types a computer number into edtNumber, for example, build the filename before linking it with AssignFile:
sFilename := 'Computer' + edtNumber.Text + '.txt'; // e.g. 'Computer34.txt'
AssignFile(tCompFile, sFilename);Writing Data to a Text File in Neat Columns
A field width indicator reserves a fixed number of character positions for a value and right-justifies it inside them, which is what makes data on separate lines line up into columns. Add a colon and a number straight after the value inside a WriteLn statement.
sSurname := 'Pillay';
WriteLn(tFile, sSurname:10); // 'Pillay' padded with 4 leading spaces to fill 10 positionsA field width on its own only right-justifies. To left-align a value instead, pad an empty string out to whatever width is still needed (using Length) and write that padding before the next field:
sName := 'Ayesha';
sSurname := 'Pillay';
WriteLn(tFile, sName, ' ':10 - Length(sName), sSurname); // surname always starts at column 11To align decimal values so the decimal points line up under each other, use the Format function to first convert the number into a fixed-width string, then write that string with WriteLn.
| Format code | Meaning |
|---|---|
% | Marks the start of a formatting instruction (not normal text) |
10 | The final string must be 10 characters wide |
2 | Show 2 digits after the decimal point |
f | The value is a floating-point (real) number |
m | Same as f, but also adds a currency symbol (e.g. R) |
WriteLn(tFile, Format('%10.2f', [rValue])); // e.g. ' 84.41'
WriteLn(tFile, Format('%12.2m', [rPrice])); // e.g. ' R14.60'Proportional fonts (like Arial) give each character a different width, so columns that line up perfectly in your code can look uneven when displayed. Set the output component's font to a fixed-width font such as Courier New so every character takes up exactly the same space and your columns line up visually.