Intermediate

I/O Operations in Pascal

Learn console input and output, formatted printing, and reading and writing text files in Pascal with Docker-ready Free Pascal examples

Input and output are where a program meets the outside world. Pascal, as an imperative and structured language, treats I/O through a small, consistent vocabulary of built-in procedures: Write and WriteLn send data out, while Read and ReadLn bring data in. The same four procedures work for the console and for files — the only difference is an optional first argument naming a file variable.

This design reflects Pascal’s teaching heritage. Rather than a sprawling I/O library, Wirth gave the language a handful of orthogonal primitives that compose predictably. A TextFile variable behaves like the console; the console behaves like a text file. Once you understand WriteLn printing to the screen, writing to disk is the same call with one extra parameter.

In this tutorial you’ll go beyond the WriteLn('Hello, World!') from the first lesson. You’ll format numbers with field-width specifiers, read typed input from the keyboard, write and read text files, and handle I/O errors the Pascal way — with compiler directives and the IOResult function. All examples run under Free Pascal (FPC).

Formatted Console Output

Pascal’s Write and WriteLn accept a colon-based formatting syntax. For any value you can append :width to set a minimum field width (right-aligned), and for real numbers a second :decimals controls fixed-point precision. This avoids the format-string mini-languages of other languages — the formatting travels with each argument.

Create a file named formatted_output.pas:

program FormattedOutput;

var
    count: Integer;
    price: Real;
    itemName: string;

begin
    count := 42;
    price := 19.99;
    itemName := 'Widget';

    { Plain output - values are converted automatically }
    WriteLn('Product: ', itemName);
    WriteLn('Count: ', count);

    { :width right-aligns an integer in a field of that width }
    WriteLn('Count padded:', count:6);

    { :width:decimals gives fixed-point formatting for reals }
    WriteLn('Price: ', price:0:2);
    WriteLn('Price padded: ', price:10:2);

    { Booleans print as TRUE or FALSE }
    WriteLn('In stock: ', count > 0);
end.

Without :0:2, a Real would print in scientific notation (for example 1.999000000000E+01), so the width/decimals specifier is the idiomatic way to show currency-style numbers.

Reading Input from the Console

ReadLn reads a whole line from standard input and converts it to the type of the target variable. Reading into a string captures the text; reading into an Integer or Real parses the number. Because ReadLn consumes the trailing newline, consecutive calls line up cleanly with the user pressing Enter after each entry.

Create a file named console_input.pas:

program ConsoleInput;

var
    userName: string;
    age: Integer;

begin
    Write('Enter your name: ');
    ReadLn(userName);

    Write('Enter your age: ');
    ReadLn(age);

    WriteLn('Hello, ', userName, '!');
    WriteLn('Next year you will be ', age + 1, ' years old.');
end.

Note the use of Write (not WriteLn) for the prompts, so the cursor stays on the same line as the user’s typing. With the input Ada followed by 36, this program produces:

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

Writing and Reading Text Files

File I/O reuses the same Write/ReadLn procedures, adding a TextFile variable as the first argument. The lifecycle is always the same four steps: AssignFile links a variable to a path, Rewrite opens it for writing (creating or truncating), Reset opens it for reading, and CloseFile flushes and releases it. The Eof function tells you when a file being read is exhausted.

Create a file named file_io.pas:

program FileIO;

var
    outFile, inFile: TextFile;
    line: string;
    lineNum: Integer;

begin
    { Open notes.txt for writing (creates or overwrites) }
    AssignFile(outFile, 'notes.txt');
    Rewrite(outFile);
    WriteLn(outFile, 'First line');
    WriteLn(outFile, 'Second line');
    WriteLn(outFile, 'Third line');
    CloseFile(outFile);
    WriteLn('Wrote 3 lines to notes.txt');

    { Reopen the same file for reading }
    AssignFile(inFile, 'notes.txt');
    Reset(inFile);
    lineNum := 0;
    while not Eof(inFile) do
    begin
        ReadLn(inFile, line);
        Inc(lineNum);
        WriteLn('Line ', lineNum, ': ', line);
    end;
    CloseFile(inFile);
end.

The while not Eof(...) loop is the canonical Pascal pattern for reading a text file line by line — it works regardless of how many lines the file contains.

Handling I/O Errors

By default, Free Pascal raises a runtime error and aborts if an I/O operation fails — for example, opening a file that doesn’t exist. To handle failures gracefully, wrap the risky call between the {$I-} and {$I+} compiler directives to switch off automatic checking, then inspect the IOResult function. IOResult returns 0 on success or an error code otherwise, and reading it clears the pending error.

Create a file named file_errors.pas:

program FileErrors;

var
    inFile: TextFile;
    line: string;

begin
    AssignFile(inFile, 'missing.txt');

    {$I-}          { turn off automatic I/O error checking }
    Reset(inFile);
    {$I+}          { turn it back on for the rest of the program }

    if IOResult <> 0 then
    begin
        WriteLn('Error: could not open missing.txt');
        Halt(1);
    end;

    ReadLn(inFile, line);
    WriteLn('First line: ', line);
    CloseFile(inFile);
end.

Because missing.txt does not exist, Reset fails, IOResult returns a non-zero code, and the program reports the error and exits with status 1 via Halt instead of crashing.

Running with Docker

You can compile and run any of these examples with the official Free Pascal image — no local install required.

1
2
3
4
5
6
# Pull the official Free Pascal image
docker pull freepascal/fpc:latest

# Compile and run the file I/O example
docker run --rm -v $(pwd):/app -w /app freepascal/fpc:latest \
    sh -c 'fpc file_io.pas && ./file_io'

To run a different example, swap the program name in both the compile and run steps, for example fpc formatted_output.pas && ./formatted_output.

Expected Output

Running the file_io.pas example produces:

Wrote 3 lines to notes.txt
Line 1: First line
Line 2: Second line
Line 3: Third line

The formatted_output.pas example produces:

Product: Widget
Count: 42
Count padded:    42
Price: 19.99
Price padded:      19.99
In stock: TRUE

And file_errors.pas (with no missing.txt present) produces:

Error: could not open missing.txt

Key Concepts

  • One vocabulary for all I/OWrite, WriteLn, Read, and ReadLn handle both the console and files; a TextFile first argument is the only difference.
  • Colon formattingvalue:width right-aligns in a field, and real:width:decimals gives fixed-point precision. Always use :decimals for reals to avoid scientific notation.
  • File lifecycleAssignFileRewrite (write) or Reset (read) → Write/ReadLnCloseFile. Forgetting CloseFile can leave buffered data unwritten.
  • Rewrite is destructive — it creates a new file or truncates an existing one. To add to a file without erasing it, use Append instead.
  • Eof drives read loopswhile not Eof(f) do ReadLn(f, line) is the standard idiom for consuming a text file of unknown length.
  • Errors are opt-in to handle — FPC aborts on I/O failure by default; bracket a call with {$I-}/{$I+} and check IOResult (which also clears the error) to recover gracefully.
  • Write vs WriteLn matters for prompts — use Write so the input cursor stays on the same line as the prompt text.

Running Today

All examples can be run using Docker:

docker pull freepascal/fpc:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining