Intermediate

I/O Operations in Fortran

Learn console and file input/output in Fortran - list-directed and formatted output, reading from stdin, writing and reading files, and I/O error handling with Docker-ready examples

Input and output are where Fortran’s scientific heritage shines. The language was built to read numeric data, crunch it, and write results — so its I/O system offers precise, column-level control over how numbers appear on screen and on disk. That control is expressed through format descriptors, a compact mini-language for laying out text and numbers.

Fortran gives you two broad styles of I/O. List-directed I/O (the * you saw in Hello World) lets the compiler choose sensible spacing — quick for debugging. Formatted I/O uses an explicit format string like '(A, I0, F7.2)' to pin down exactly how each value is rendered. As a statically typed, procedural language, Fortran treats each I/O channel as a numbered unit, and files are attached to units with open and detached with close.

In this tutorial you’ll write formatted output to the console, read values from standard input, write structured data to a file, and read it back — handling end-of-file and error conditions along the way. Every example compiles cleanly with gfortran and runs in the official GCC Docker image.

Formatted Console Output

Hello World used print * for quick output. For real programs you’ll usually want write with a format string so numbers line up predictably. The most common format descriptors are:

  • A — character (string) data
  • I — integer (e.g. I5 = width 5; I0 = minimum width)
  • F — fixed-point real (e.g. F7.2 = width 7, 2 decimals)
  • X — a blank space (e.g. 2X = two spaces)

Create a file named io_output.f90:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
program io_output
    implicit none
    integer :: apples = 5
    real :: price = 2.75

    ! List-directed output: the compiler picks the spacing
    print *, "Fortran I/O demo"

    ! Formatted output: you control the layout exactly
    write(*, '(A, I0, A)') "You have ", apples, " apples."
    write(*, '(A, F5.2)') "Price each: $", price
    write(*, '(A, F7.2)') "Total cost: $", apples * price
end program io_output

Here I0 prints the integer with no padding, while F5.2 reserves five columns for a number with two decimals. Because apples is an integer and price is a real, apples * price promotes the result to real automatically.

Reading Input from Standard Input

The read statement pulls data from a unit; the * unit is standard input. List-directed reads (read(*, *)) split on whitespace and convert to the target type, while read(*, '(A)') grabs a whole line of text.

Create a file named io_input.f90:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
program io_input
    implicit none
    character(len=50) :: name
    integer :: age

    write(*, '(A)') "Enter your name:"
    read(*, '(A)') name

    write(*, '(A)') "Enter your age:"
    read(*, *) age

    write(*, '(A, A, A, I0, A)') "Hello, ", trim(name), &
        "! You are ", age, " years old."
end program io_input

The & at the end of the line is Fortran’s free-form line continuation, letting a single statement span two lines. trim(name) strips the trailing blanks that fill the fixed-length character(len=50) variable.

Writing to a File

To work with a file, attach it to a unit with open, write to that unit, then close it. The newunit= specifier (Fortran 2008) asks the runtime for a free unit number, so you never have to guess one. status="replace" creates the file fresh (overwriting any existing copy).

Create a file named io_write_file.f90:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
program io_write_file
    implicit none
    integer :: unit_num, i

    open(newunit=unit_num, file="output.txt", status="replace", action="write")

    write(unit_num, '(A)') "Report generated by Fortran"
    write(unit_num, '(A)') "----------------------------"

    do i = 1, 3
        write(unit_num, '(A, I0, A, I0)') "Line ", i, ": value = ", i * i
    end do

    close(unit_num)

    print *, "File written successfully."
end program io_write_file

Every write(unit_num, ...) targets output.txt instead of the screen. After close, the file is flushed and safe to read.

Reading a File with Error Handling

Reading is the mirror image: open with status="old" (the file must already exist), then loop read calls until you hit end-of-file. The iostat= specifier captures the status of each I/O operation — it’s 0 on success, negative at end-of-file, and positive on a genuine error. This is Fortran’s idiomatic way to handle I/O safely without crashing.

Create a file named io_read_file.f90:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
program io_read_file
    implicit none
    integer :: unit_num, ios
    character(len=100) :: line

    open(newunit=unit_num, file="output.txt", status="old", &
         action="read", iostat=ios)

    if (ios /= 0) then
        print *, "Error: could not open output.txt"
        stop 1
    end if

    ! Read line by line until end-of-file
    do
        read(unit_num, '(A)', iostat=ios) line
        if (ios /= 0) exit
        print '(A)', trim(line)
    end do

    close(unit_num)
end program io_read_file

The bare do with if (ios /= 0) exit is the standard read loop: it keeps going until read reports a non-zero status (end-of-file or error), then breaks out. This example reads the very output.txt produced by the previous program.

Running with Docker

Run each example with the official GCC image, which includes gfortran. Compile to an executable, then run it — all in one container.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Pull the official image
docker pull gcc:latest

# 1. Formatted console output
docker run --rm -v $(pwd):/app -w /app gcc:latest \
    sh -c "gfortran -o io_output io_output.f90 && ./io_output"

# 2. Reading from standard input (-i keeps stdin open; pipe the answers in)
docker run --rm -i -v $(pwd):/app -w /app gcc:latest \
    sh -c "gfortran -o io_input io_input.f90 && printf 'Alice\n30\n' | ./io_input"

# 3. Write a file, then show its contents
docker run --rm -v $(pwd):/app -w /app gcc:latest \
    sh -c "gfortran -o io_write_file io_write_file.f90 && ./io_write_file && cat output.txt"

# 4. Read that file back (output.txt persists via the mounted volume)
docker run --rm -v $(pwd):/app -w /app gcc:latest \
    sh -c "gfortran -o io_read_file io_read_file.f90 && ./io_read_file"

Expected Output

io_output — note the leading space on the list-directed line and the aligned decimals:

 Fortran I/O demo
You have 5 apples.
Price each: $ 2.75
Total cost: $  13.75

io_input (with Alice and 30 piped in) — the prompts and the result appear in order:

Enter your name:
Enter your age:
Hello, Alice! You are 30 years old.

io_write_file — the console message, followed by the cat output.txt contents:

 File written successfully.
Report generated by Fortran
----------------------------
Line 1: value = 1
Line 2: value = 4
Line 3: value = 9

io_read_file — each line echoed straight from the file:

Report generated by Fortran
----------------------------
Line 1: value = 1
Line 2: value = 4
Line 3: value = 9

Key Concepts

  • Two output styles: use print * / read * (list-directed) for quick work, and write / read with a format string for precise, aligned layout.
  • Format descriptors are the heart of Fortran I/O: A for text, I for integers, F for reals, X for spaces — combined in a string like '(A, I0, F7.2)'.
  • Units connect to files: open attaches a file to a unit, write/read use that unit, and close detaches it. newunit= picks a free unit for you automatically.
  • iostat= is your error handler: it returns 0 on success, a negative value at end-of-file, and a positive value on error — check it instead of letting I/O crash the program.
  • The read-until-EOF loop — a bare do with read(..., iostat=ios) and if (ios /= 0) exit — is the canonical way to consume a file of unknown length.
  • trim() matters for I/O: fixed-length character variables are blank-padded, so trim() removes trailing spaces before printing or comparing.
  • List-directed output adds a leading space in column one — a carriage-control holdover from line-printer days — while print '(A)', ... gives you exact column control.

Running Today

All examples can be run using Docker:

docker pull gcc:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining