Intermediate

I/O Operations in Delphi

Learn console and file input/output in Delphi with WriteLn, ReadLn, TextFile handling, formatted output, and I/O error handling using Docker-ready examples

Input and output are how a program talks to the outside world — reading what a user types, printing results to the terminal, and persisting data to disk. Delphi inherits Pascal’s clean, readable I/O routines while layering on SysUtils conveniences like the Format function and structured exception handling for I/O errors.

As a multi-paradigm language with strong static typing, Delphi’s I/O reflects its heritage: the core Write, WriteLn, Read, and ReadLn procedures come straight from Pascal, while file access uses typed file handles (TextFile) that you associate, open, use, and close explicitly. Because Delphi is strongly typed, ReadLn can read directly into an Integer or Double variable and perform the conversion for you.

In this tutorial you’ll learn how to produce formatted console output, read input from the keyboard, write and read text files, and handle the errors that inevitably occur when working with the file system. Every example runs under Free Pascal’s Delphi compatibility mode (-Mdelphi), so no commercial Delphi installation is required.

Console Output

You already met WriteLn in Hello World. The full picture: Write outputs text without a trailing newline, while WriteLn appends one. Combined with SysUtils.Format — Delphi’s printf-style formatter — you get precise control over how values appear.

Create a file named console_output.dpr:

program ConsoleOutput;

{$APPTYPE CONSOLE}

uses
  SysUtils;  // Provides the Format function

var
  Quantity: Integer;
  Price: Double;

begin
  // Write does NOT add a newline; WriteLn does
  Write('Loading');
  Write('...');
  WriteLn(' done!');

  Quantity := 3;
  Price := 4.99;

  // Format provides printf-style substitution
  WriteLn(Format('Items: %d', [Quantity]));
  WriteLn(Format('Price: $%.2f', [Price]));
  WriteLn(Format('Total: $%.2f', [Quantity * Price]));

  // Field width and alignment: %-10s left-justifies, %5d right-justifies
  WriteLn(Format('%-10s|%5d', ['Left', 42]));
end.

Key format specifiers:

  • %d — integer
  • %.2f — floating-point with 2 decimal places
  • %s — string
  • %-10s — string left-justified in a 10-character field
  • %5d — integer right-justified in a 5-character field

Reading Console Input

To read from the keyboard, use ReadLn. Because Delphi is strongly typed, reading into a string captures the whole line, while reading into a numeric variable performs the conversion automatically.

Create a file named console_input.dpr:

program ConsoleInput;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  Name: string;
  Age: Integer;

begin
  Write('Enter your name: ');
  ReadLn(Name);          // Reads an entire line into a string

  Write('Enter your age: ');
  ReadLn(Age);           // Reads and converts directly to Integer

  WriteLn(Format('Hello, %s!', [Name]));
  WriteLn(Format('Next year you will be %d.', [Age + 1]));
end.

This program is interactive. A sample session where the user types Ada and 36:

Enter your name: Ada
Enter your age: 36
Hello, Ada!
Next year you will be 37.

You can feed input non-interactively by piping it into the program, which is handy for testing:

1
printf 'Ada\n36\n' | ./console_input

Writing to Files

Delphi’s classic text file I/O follows a four-step pattern: associate a variable with a filename (AssignFile), open it (Rewrite to create/overwrite, or Append to add to the end), use it with Write/WriteLn, and close it (CloseFile). A try..finally block guarantees the file is closed even if an error occurs.

Create a file named file_write.dpr:

program FileWrite;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  OutFile: TextFile;
  I: Integer;

begin
  AssignFile(OutFile, 'notes.txt');  // Associate variable with filename
  Rewrite(OutFile);                  // Create (or overwrite) the file
  try
    WriteLn(OutFile, 'Delphi I/O Notes');
    WriteLn(OutFile, '================');
    for I := 1 to 3 do
      WriteLn(OutFile, Format('Line %d', [I]));
  finally
    CloseFile(OutFile);              // Always close, even on error
  end;

  WriteLn('Wrote notes.txt');
end.

Notice that WriteLn(OutFile, ...) is the same procedure used for the console — passing a TextFile as the first argument simply redirects the output to that file.

Reading from Files

Reading mirrors writing: associate, open with Reset, read line by line until Eof (end of file) returns True, then close. Guard against a missing file first with FileExists.

Create a file named file_read.dpr:

program FileRead;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  InFile: TextFile;
  Line: string;
  Count: Integer;

begin
  if not FileExists('notes.txt') then
  begin
    WriteLn('notes.txt not found. Run file_write first.');
    Halt(1);
  end;

  AssignFile(InFile, 'notes.txt');
  Reset(InFile);            // Open the file for reading
  Count := 0;
  try
    while not Eof(InFile) do
    begin
      ReadLn(InFile, Line); // Read one line at a time
      Inc(Count);
      WriteLn(Format('%d: %s', [Count, Line]));
    end;
  finally
    CloseFile(InFile);
  end;

  WriteLn(Format('Read %d lines.', [Count]));
end.

Handling I/O Errors

By default, Delphi mode compiles with I/O checking on ({$I+}), so a failed file operation raises an EInOutError exception. Wrapping risky operations in try..except lets your program recover gracefully instead of crashing.

Create a file named io_operations.dpr:

program IoOperations;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  DataFile: TextFile;
  Line: string;

begin
  // Attempt to open a file that does not exist
  AssignFile(DataFile, 'missing.txt');
  try
    Reset(DataFile);          // Raises EInOutError because the file is missing
    try
      ReadLn(DataFile, Line);
      WriteLn('Read: ', Line);
    finally
      CloseFile(DataFile);
    end;
  except
    on E: EInOutError do
      WriteLn('I/O error: could not open missing.txt');
  end;

  WriteLn('Program continues after handling the error.');
end.

The on E: EInOutError do clause catches only I/O-related exceptions, letting you report a friendly message while the program keeps running. This structured approach is one of the biggest advantages Delphi/Object Pascal has over standard Pascal, which had no exception mechanism at all.

Running with Docker

Compile and run each example in sequence using the Free Pascal image. Because file_read.dpr depends on the file created by file_write.dpr, run the write step first.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Pull the official image
docker pull freepascal/fpc:3.2.2-slim

# Console output
docker run --rm -v $(pwd):/app -w /app freepascal/fpc:3.2.2-slim \
    sh -c "fpc -Mdelphi console_output.dpr && ./console_output"

# Write a file, then read it back
docker run --rm -v $(pwd):/app -w /app freepascal/fpc:3.2.2-slim \
    sh -c "fpc -Mdelphi file_write.dpr && ./file_write && \
           fpc -Mdelphi file_read.dpr && ./file_read"

# I/O error handling
docker run --rm -v $(pwd):/app -w /app freepascal/fpc:3.2.2-slim \
    sh -c "fpc -Mdelphi io_operations.dpr && ./io_operations"

The executable is named after the source file (without the .dpr extension), so console_output.dpr compiles to ./console_output.

Expected Output

Running console_output:

Loading... done!
Items: 3
Price: $4.99
Total: $14.97
Left      |   42

Running file_write followed by file_read:

Wrote notes.txt
1: Delphi I/O Notes
2: ================
3: Line 1
4: Line 2
5: Line 3
Read 5 lines.

Running io_operations:

I/O error: could not open missing.txt
Program continues after handling the error.

Key Concepts

  • Write vs WriteLnWrite prints without a trailing newline; WriteLn adds one. The same procedures write to both the console and files.
  • Format for formatted outputSysUtils.Format gives printf-style control with specifiers like %d, %.2f, %s, and field widths such as %-10s and %5d.
  • Strong typing helps inputReadLn reads directly into typed variables, converting text to Integer or Double automatically instead of requiring a manual parse.
  • The file lifecycle — text file I/O follows AssignFile → open (Rewrite/Append/Reset) → use → CloseFile. Redirect any Write/WriteLn to a file by passing the TextFile variable as the first argument.
  • Always close in finally — a try..finally block guarantees CloseFile runs even if an exception is raised mid-operation.
  • Check before you readFileExists and the Eof function help you avoid errors before and during reading.
  • Structured I/O error handling — with I/O checking on (the Delphi-mode default), failed operations raise EInOutError, which you can catch with try..except on E: EInOutError to recover gracefully.

Running Today

All examples can be run using Docker:

docker pull freepascal/fpc:3.2.2-slim
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining