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.

T2Term 2 · Reading & Writing Text Files

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.

ANALOGY

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:

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

ANALOGY

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:

Key Procedures and Functions

CommandPurpose
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

Delphi — read all lines
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;

LoadFromFile and SaveToFile — Not Usable in Exams or PAT

🚫 You may NOT use LoadFromFile or SaveToFile in exams or PAT tasks — they will not be assessed.

A Memo or RichEdit's Lines property has two built-in methods, LoadFromFile and SaveToFile, that dump a whole text file onto the screen (or the screen into a file) in a single line, skipping AssignFile/Reset/ReadLn/EOF entirely.

Delphi — what this shortcut looks like
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 file
Do not use LoadFromFile / SaveToFile

LoadFromFile and SaveToFile cannot be assessed in exams or PAT tasks and must not be used in your practical work at all — not even as a shortcut for the parts of a task that just display or save a file as-is. Every file operation, from the simplest read to the most complex report, must be written from first principles using AssignFile, Reset/Rewrite/Append, ReadLn/WriteLn, EOF and CloseFile — the technique shown earlier on this page. This example is here only so you recognise the shortcut and know to avoid it, not to use it.

EOF vs Eoln — Reading Multiple Values on One Line

EOF(F) checks whether you have reached the end of the whole file. But what if a single line holds several values you need to read one at a time, and you don't know in advance how many there are? Delphi has a matching function for exactly this: Eoln(F) — short for "End Of Line" — which returns True once you have read everything on the current line, even though the file itself is not finished yet.

FunctionReturns True when…Used to control…
EOF(F)There are no lines left to read in the whole fileThe outer loop — moves from one line to the next
Eoln(F)You have reached the end of the current line (the file still has more lines after this one)The inner loop — moves from one value to the next value on the same line

Consider a file where each line holds an unknown number of test scores separated by spaces:

Data.txt
85 72 90 61
78 88

Because every line has a different number of values, ReadLn on its own cannot pull them out one at a time. The fix is to nest a value-by-value loop (controlled by Eoln) inside the line-by-line loop (controlled by EOF):

Delphi — EOF and Eoln working together
var
  F: TextFile;
  iScore, iTotal, iCount: Integer;
begin
  iTotal := 0;
  iCount := 0;
  AssignFile(F, 'Data.txt');
  Reset(F);
  while not EOF(F) do          // outer loop: one line at a time
  begin
    while not Eoln(F) do       // inner loop: one value at a time on this line
    begin
      Read(F, iScore);              // Read (not ReadLn) leaves the cursor on the same line
      iTotal := iTotal + iScore;
      iCount := iCount + 1;
    end;
    ReadLn(F);                      // step past the end-of-line marker onto the next line
  end;
  CloseFile(F);
  ShowMessage('Average: ' + FloatToStr(iTotal / iCount));
end;
Read vs ReadLn inside the inner loop

Inside the Eoln loop you must use Read, not ReadLn. Read takes just the next value and leaves the file cursor exactly where it stopped, ready for the next Read. ReadLn would jump straight to the next line, throwing away any values still waiting on the current one. The lone ReadLn(F); after the inner loop ends is what actually steps the cursor past the invisible end-of-line marker, so the outer EOF loop can correctly move on to the next line.

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.

Delphi — try..except when reading
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 vs try..except

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)

Delphi — create / 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

Delphi — add to end
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

Delphi — read Name,Mark pairs
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;
Always CloseFile!

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.

The scenario

A text file marks.txt stores one learner per line as Surname,Name,Mark:

marks.txt
Coetzee,Mari,72
Naidoo,Priya,88
Botha,Jan,64
Khumalo,Sipho,91

Task: 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

Delphi — global declarations
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 read

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

Delphi — read into parallel arrays
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).

Delphi — display "M. Surname: 72%"
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.)

Delphi — average of the mark array
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.

Delphi — highest mark via parallel arrays
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]) + '%)');
What this one question tests

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.

Delphi — write report.txt
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.

Delphi — split marks.txt into Pass.txt and Fail.txt
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;
Rewrite wipes existing data

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.

Building a filename at runtime

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:

Delphi — dynamic filename
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.

Delphi — field width indicator
sSurname := 'Pillay';
WriteLn(tFile, sSurname:10);   // 'Pillay' padded with 4 leading spaces to fill 10 positions

A 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:

Delphi — left-align with Length
sName := 'Ayesha';
sSurname := 'Pillay';
WriteLn(tFile, sName, ' ':10 - Length(sName), sSurname);   // surname always starts at column 11

To 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 codeMeaning
%Marks the start of a formatting instruction (not normal text)
10The final string must be 10 characters wide
2Show 2 digits after the decimal point
fThe value is a floating-point (real) number
mSame as f, but also adds a currency symbol (e.g. R)
Delphi — Format function
WriteLn(tFile, Format('%10.2f', [rValue]));    // e.g. '     84.41'
WriteLn(tFile, Format('%12.2m', [rPrice]));   // e.g. '      R14.60'
Use a fixed-width font to check your columns

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.