Read and write text files in Modern Fortran

Utpal Kumar   5 minute read      

In this post, I will try to give you a quick look of the key basic concepts in modern Fortran so that we can write a simple text file using the Fortran completely. Then we will read the file using Fortran.

Key idea — a unit number is your handle to a file. In Fortran you never touch a file directly. You open a unit (an integer channel) and attach it to a file, do all your write/read through that unit number, then close it. Everything hinges on four statements — open, write, read, close — and a format descriptor that says how the bytes should look on the page.

Fortran file-I/O lifecycle Open connects a unit number to a file, write and read move data through that unit, and close releases it. open(unit=N, file=…) connect N ↔ file write(N,…) / read(N,…) move data through N close(N) release the channel the unit number N is your handle to the file — valid only between open and close
Every file operation follows this lifecycle: open a unit, move data through it, close it.

Basic fortran program

Let us write a program to print the area of a circle in your stdout (your terminal).

program maths
    implicit none
    real :: r, area
    real, parameter :: pi = 4.*atan(1.)
    r = 2.0
    area = pi*r**2

    print*,'The area of a circle of radius ',r,' is ',area
end program maths

Notice that I have defined a parameter using the trigonometric function atan. The good thing about Fortran is that the mathematical functions are embedded into it. (4.*atan(1.) is just a portable way to write $\pi$, since $\tan(\pi/4) = 1$.)

Let us compile this code and run it:

gfortran -o circarea circarea.f90 
./circarea

This returns:

 The area of a circle of radius    2.00000000      is    12.5663710 

The results are correct but as you may have noticed that the output is not formatted properly. So, let us fix that.

program maths
    implicit none
    real :: r, area
    real, parameter :: pi = 4.*atan(1.)
    r = 2.0
    area = pi*r**2

    write(*,1) 'The area of a circle of radius ',r,' is ',area
    1 format(A,F3.1,A,F5.2)
end program maths

This time we have used the write to output the result instead of print. You can also notice that we used the format to properly format the output:

The area of a circle of radius 2.0 is 12.57

print vs write, and the * unit. print always goes to standard output; write lets you choose where the data goes by naming a unit. write(*,...) means “the default output unit” (your terminal) — the same * you’ll swap for a real unit number when you write to a file below.

Fortran format descriptors

Descriptor Description Example
I integer output print "(3i5)", i, j, k repeat i5 three times
F real number output print "(2f12.3)",val1, val2 repeat 12 field width twice with precision 3
E real output in exponential notation print "(e10.3)",123456.0 gives ‘0.123e+06’
A character output print "(2a)", 'earthinversion' gives ‘earthinversionearthinversion’
X space output print "(a,2X,a)", 'earthinversion', 'earthinversion' gives ‘earthinversion earthinversion’
/ insert blank lines print "(a,/,a)", str, str

Reading F5.2 as “a real number in a field 5 characters wide with 2 decimals” is the whole trick to these descriptors — the letter picks the type, the numbers pick the width and precision.

Writing to a file

Writing and later you will see that the “reading” is about using the open, close, write and read statements.

program maths
    implicit none
    real :: r, area
    real, parameter :: pi = 4.*atan(1.)
    r = 2.0
    area = pi*r**2

    ! write to ascii file
    open(unit=1,file='results.out',status='replace',form='formatted')
    write(1,11) 'The area of a circle of radius ',r,' is ',area
    11 format(A,F3.1,A,F5.2)
    close(1)
end program maths

Here write(1,11) sends the formatted line to unit 1 (attached to results.out) instead of the terminal. status='replace' overwrites the file if it already exists.

Reading from the file

program maths
    implicit none
    real :: r, area
    real, parameter :: pi = 4.*atan(1.)
    character(len=20) :: filename
    character(len = 45) :: str1
    r = 2.0
    area = pi*r**2
    filename='results.out'


    ! write to ascii file
    open(unit=1,file=filename,status='replace',form='formatted') !rewrite the file if exists
    write(1,11) 'The area of a circle of radius ',r,' is ',area
    11 format(A,F3.1,A,F5.2)
    close(1)

    ! read from ascii file
    open(unit=2,file=filename,status='old') !read from the existing file
    read(2,*) str1
    close(2)

    write(*,*) str1
end program maths

This program writes a string into a file ‘results.out’ and then read it back into a string named str1. Notice that we have defined the length of the string long enough (45 chars) to read all the string data.

Quick check: Why is str1 declared as character(len=45) rather than, say, len=10?

  • Fortran requires all strings to be exactly 45 characters
  • The buffer must be long enough to hold the whole line being read; a too-short length would truncate the string
  • 45 is the maximum length Fortran allows
  • It sets the unit number for reading

Read and write the numeric data into a file

This time instead of writing it as a string, we will only write the numeric data into the file and read those data back into another variables.

program maths
    implicit none
    real :: r, area
    real :: r_rd, area_rd
    real, parameter :: pi = 4.*atan(1.)
    character(len=20) :: filename
    r = 2.0
    area = pi*r**2
    filename='results.out'


    ! write to ascii file
    open(unit=1,file=filename,status='replace',form='formatted') !rewrite the file if exists
    write(1,11) r,area
    11 format(F3.1,F5.2)
    close(1)
    
    ! read from ascii file
    open(unit=2,file=filename,status='old') !read from the existing file
    read(2,11) r_rd, area_rd
    close(2)
    
    write(*,12) r_rd, area_rd
    12 format(F3.1,2X,F5.2)
end program maths

Because we wrote the numbers with the format 11, we read them back with the same descriptor 11 — the format is the contract that keeps the written and read layouts in sync.

Modern touch: let the compiler pick the unit with `newunit`

The examples above hard-code unit=1 and unit=2. That works, but small integers can clash with pre-connected units (historically 5 = stdin, 6 = stdout). Since Fortran 2008, you can ask the runtime for a guaranteed-free unit and catch errors with iostat:

integer :: u, ios
open(newunit=u, file='results.out', status='old', iostat=ios)
if (ios /= 0) stop 'could not open results.out'
read(u,*) r_rd, area_rd
close(u)

newunit=u returns a unique negative unit number into u, so you never have to guess a safe number — the modern, collision-proof way to open files. All modern compilers (e.g. gfortran) support it.

Recap

  • Four verbs run the show: open (attach a unit to a file), write/read (move data through the unit), close (detach).
  • Formats are contracts. A descriptor like F5.2 fixes width and precision; read data back with the same format you wrote it with.
  • * means “default”: write(*,*) is free-format to the terminal; swap the first * for a unit number to target a file.
  • Prefer newunit= in new code. It hands you a safe unit automatically (Fortran 2008+), and pairing it with iostat= makes your I/O robust.

Where to go next

Disclaimer of liability

The information provided by the Earth Inversion is made available for educational purposes only.

Whilst we endeavor to keep the information up-to-date and correct. Earth Inversion makes no representations or warranties of any kind, express or implied about the completeness, accuracy, reliability, suitability or availability with respect to the website or the information, products, services or related graphics content on the website for any purpose.

UNDER NO CIRCUMSTANCE SHALL WE HAVE ANY LIABILITY TO YOU FOR ANY LOSS OR DAMAGE OF ANY KIND INCURRED AS A RESULT OF THE USE OF THE SITE OR RELIANCE ON ANY INFORMATION PROVIDED ON THE SITE. ANY RELIANCE YOU PLACED ON SUCH MATERIAL IS THEREFORE STRICTLY AT YOUR OWN RISK.


Leave a comment