Fortran files
last modified November 14, 2026
A variable disappears when the program ends; a file does not. A
file is a named collection of data stored outside the program, and in
Fortran it is seen as a sequence of records, where a text file has one
record per line. Before a program can use a file it must open it,
which connects the file to a small integer called a unit; from then
on every read and write mentions the unit instead of
the name. This article shows how to create, read, append and copy text files,
how to choose the right status and action, how to
notice a failed open with iostat and iomsg, and how
to make a temporary file that disappears when it is closed.
Writing a text file
The open statement below uses three specifiers. file=
gives the name, status='replace' says that the file should be
created, or emptied if it already exists, and newunit=u asks the
runtime to pick a free unit and store its number in u. The
returned number is negative, which is the convention for units reserved this
way; it cannot clash with the small positive numbers that older programs use
by hand. The two write statements then place two integers in each
record, and close writes the remaining data out and releases the
unit.
program f1
implicit none
integer :: u, i
open (newunit=u, file='squares.txt', status='replace')
do i = 1, 5
write (u, '(i3, i6)') i, i * i
end do
close (u)
print *, 'wrote 5 records'
end program f1
The format (i3, i6) gives the first column three characters and
the second six, so the numbers line up on their last digit. The transcript
runs the program and then shows the file with cat, a standard
Unix command that copies a file to the screen. Nothing about the output
depends on the compiler: the format fixes every character, including the two
blanks before each single-digit square.
$ gfortran f1.f90 -o f1 $ ./f1; cat squares.txt wrote 5 records 1 1 2 4 3 9 4 16 5 25
Reading a file line by line
Reading is the mirror image: open with status='old'
insists that the file already exists, and each read takes one
record. The format '(a)' copies the whole record into a character
variable without trying to interpret it, which is the text equivalent of
reading a line in another language. The loop has no counter because the number
of records is not known in advance; it stops when iostat reports
that the last record has been passed.
program f2
implicit none
character(len=40) :: line
integer :: u, ios, n
open (newunit=u, file='squares.txt', status='replace')
write (u, '(a)') 'one 1'
write (u, '(a)') 'two 4'
write (u, '(a)') 'three 9'
close (u)
n = 0
open (newunit=u, file='squares.txt', status='old')
do
read (u, '(a)', iostat=ios) line
if (ios /= 0) exit
n = n + 1
print *, n, '[', trim(line), ']'
end do
close (u)
end program f2
The first half of the program writes the file that the second half reads, so
the example can run in an empty directory. The status is zero after each
successful read and negative at the end of the file, which is why the test is
ios /= 0 rather than ios < 0: a positive status
would mean a damaged record, and that should also stop the loop. Because the
records are read as text, their content does not matter to the program; the
brackets show that the trailing blanks of line were trimmed away
before printing.
$ gfortran f2.f90 -o f2
$ ./f2
1 [one 1]
2 [two 4]
3 [three 9]
Existing files and open errors
A real program cannot assume that the file it wants is there, or that the
directory is writable. Two tools answer such questions before the program
commits to a course of action: inquire reports properties of a
file without opening it, and the iostat= and iomsg=
specifiers of open turn a failure into a status code and a
message instead of an unconditional stop. iostat is zero on
success and positive or negative on failure, and the character variable given
to iomsg receives a description written by the runtime. The
related specifier action= states what the program intends to do
with the file: 'read', 'write' or
'readwrite'. It is a promise that the runtime can check, and an
open that asks for 'read' on a file that does not exist, or for
'write' on a read-only file, fails at once instead of at the first
transfer.
program f3
implicit none
integer :: u, ios
logical :: exists
character(len=100) :: msg
open (newunit=u, file='report.txt', status='replace')
write (u, '(a)') 'Fortran file test'
close (u)
inquire (file='report.txt', exist=exists)
print *, 'exists: ', exists
inquire (file='missing.txt', exist=exists)
print *, 'missing exists: ', exists
open (newunit=u, file='missing.txt', status='old', iostat=ios, iomsg=msg)
print *, 'open status:', ios
if (ios /= 0) print *, trim(msg)
end program f3
The first file exists because the program has just written it, while the second
one does not, and inquire reports the difference without any error.
The open of the missing file then fails with status 2, and the message says
what went wrong in words. The number itself is processor dependent, so a
portable program tests it against zero and prints the message rather than
relying on the value; the text of the message is also specific to the runtime,
which is why the article quotes it only as an example.
$ gfortran f3.f90 -o f3 $ ./f3 exists: T missing exists: F open status: 2 Cannot open file 'missing.txt': No such file or directory
Appending to a file
Opening a file for writing normally starts at its beginning and overwrites
whatever was there. The specifier position='append' changes that:
the file is opened with the position after the last record, so every write adds
to the end and the earlier contents survive. The program below builds a small
log in three separate opens, which is how a program that runs many times can
keep adding lines to the same file.
program f4
implicit none
integer :: u
character(len=80) :: line
integer :: ios, lines
open (newunit=u, file='log.txt', status='replace')
write (u, '(a)') 'start'
close (u)
open (newunit=u, file='log.txt', position='append')
write (u, '(a, i0)') 'step ', 1
write (u, '(a, i0)') 'step ', 2
close (u)
open (newunit=u, file='log.txt', position='append')
write (u, '(a)') 'end'
close (u)
lines = 0
open (newunit=u, file='log.txt', status='old')
do
read (u, '(a)', iostat=ios) line
if (ios /= 0) exit
lines = lines + 1
print *, lines, trim(line)
end do
close (u)
end program f4
The first open creates the file with one record, the second adds two, and the
third adds one more, so the final file has four records. Without
position='append' the second open would place the position at the
start and start would be overwritten by step 1.
Note that status='replace' appears only in the first open; using it
again later would delete the records written before.
$ gfortran f4.f90 -o f4
$ ./f4
1 start
2 step 1
3 step 2
4 end
Reading numbers from a file
A list-directed read from a file works like a list-directed read
from the keyboard, and it consumes one whole record per statement. That is the
point that surprises newcomers: if a record holds two numbers and the statement
asks for one variable, the second number is lost with the rest of the record.
The program below therefore writes one value per line and reads them in a loop,
accumulating a sum, a maximum and a count until the file ends.
program f5
implicit none
real :: x, total, vmax
integer :: u, ios, n
open (newunit=u, file='values.txt', status='replace')
write (u, '(a)') '1.5'
write (u, '(a)') '2.5'
write (u, '(a)') '3.0'
write (u, '(a)') '-1.0'
close (u)
total = 0.0
vmax = -huge(1.0)
n = 0
open (newunit=u, file='values.txt', status='old')
do
read (u, *, iostat=ios) x
if (ios /= 0) exit
n = n + 1
total = total + x
vmax = max(vmax, x)
end do
close (u)
print *, 'values:', n
print *, 'total: ', total
print *, 'mean: ', total / n
print *, 'max: ', vmax
end program f5
Four values produce a total of 6, a mean of 1.5 and a maximum of 3. The
starting value of vmax is -huge(1.0), the most
negative real of the default kind, so the first value always replaces it. If a
record cannot be converted, the status becomes positive and the loop stops as
if the file had ended; a program that must survive damaged data reads whole
lines and parses them itself, as the
CSV article shows.
$ gfortran f5.f90 -o f5 $ ./f5 values: 4 total: 6.00000000 mean: 1.50000000 max: 3.00000000
Copying a file
Copying a text file means reading one record and writing it to another file
until the input ends. Two units are open at the same time, each with its own
position, and the loop transfers the records one by one. The blank padding of
the fixed-length line is removed with trim before the
write, so the copy does not accumulate trailing spaces that the original did
not have.
program f6
implicit none
character(len=80) :: line
integer :: src, dst, ios, n
open (newunit=src, file='source.txt', status='replace')
write (src, '(a)') 'alpha'
write (src, '(a)') 'beta'
write (src, '(a)') 'gamma'
close (src)
open (newunit=src, file='source.txt', status='old')
open (newunit=dst, file='copy.txt', status='replace')
n = 0
do
read (src, '(a)', iostat=ios) line
if (ios /= 0) exit
write (dst, '(a)') trim(line)
n = n + 1
end do
close (src)
close (dst)
print *, 'copied', n, 'records'
end program f6
The input unit is opened with status='old' and the output unit with
status='replace', so the source is protected from being emptied by
mistake. Three records are transferred and the program reports the count.
Closing both units matters for a program that copies many files, because the
number of units a process may have open at once is limited; closing also
guarantees that the last record actually reached the disk before the program
moves on.
$ gfortran f6.f90 -o f6 $ ./f6 copied 3 records
A formatted data file
Files that other programs read are usually written with a fixed layout rather
than with list-directed output, because the layout must stay the same from run
to run. The program below writes a header and three rows with
(a8, f8.2), so the names occupy eight characters and the scores
eight more with two decimals. The same widths are used for the header, which is
why the word score sits above the numbers.
program f7
implicit none
character(len=10) :: name(3)
real :: score(3)
integer :: u, i
name = ['alice', 'bob ', 'carol']
score = [91.5, 78.25, 84.0]
open (newunit=u, file='scores.txt', status='replace')
write (u, '(a8, a8)') 'name', 'score'
do i = 1, 3
write (u, '(a8, f8.2)') trim(name(i)), score(i)
end do
close (u)
print *, 'wrote scores.txt'
end program f7
The header uses a8 twice because both items are character strings;
the data rows use a8 and f8.2 because the second item
is a real number. Mixing the two up is a common mistake: the compiler cannot
check the format against the values at compile time, and a wrong descriptor
stops the program at run time. The file on disk contains exactly the columns
shown by cat, with four blanks in front of name
because the word has four letters in a field of eight.
$ gfortran f7.f90 -o f7
$ ./f7; cat scores.txt
wrote scores.txt
name score
alice 91.50
bob 78.25
carol 84.00
Temporary and scratch files
Some files are working space rather than results, and they should not be left
behind. Two mechanisms cover that. A file opened normally can be removed when
it is closed, with close(unit, status='delete'), and a file opened
with status='scratch' has no name at all: the runtime creates it,
lets the program use it, and deletes it when the program closes it or exits.
Both are useful for intermediate results that are too large to keep in memory.
program f8
implicit none
integer :: u
logical :: exists
open (newunit=u, file='temporary.txt', status='replace')
write (u, '(a)') 'scratch data'
close (u, status='delete')
inquire (file='temporary.txt', exist=exists)
print *, 'file still there:', exists
open (newunit=u, status='scratch')
write (u, '(a)') 'invisible'
rewind (u)
print *, 'scratch works'
close (u)
end program f8
After the first close the file is gone, so inquire reports
F. The scratch file is invisible in the directory from the start,
and rewind moves its position back to the first record so that it
could be read again. A scratch file cannot be reopened later, since it has no
name; if the data must survive the close, a named file is the only option.
$ gfortran f8.f90 -o f8 $ ./f8 file still there: F scratch works
Next steps
Text files store characters, but numbers can also be written in their internal
binary form, which is smaller, faster and exact. The
stream and binary files article covers
access='stream', unformatted records and direct access. The
namelists article shows a compact format for
configuration files, and the CSV and text data
article reads a whole table into arrays. The formats used here are explained in
the formatted output article, and the
reading input article applies the same
iostat loop to the keyboard.
Source
Fortran 2018 standard (J3/18-007r1)
Fortran-lang: learning resources
In this article we have opened files with newunit, chosen between
status='replace', 'old' and 'scratch',
written and read text records, appended to a log, copied a file, produced a
fixed-layout data file, and handled a failed open with
iostat and iomsg. The previous article,
Reading input in Fortran, shows the same
statements reading from the standard input.
Author
List all Fortran tutorials.