File Handling & I/O
Units and OPEN
Every file is attached to an unit number — a small integer your program chooses. Units 5 (stdin) and 6 (stdout) are preconnected. open connects a unit to a file name with a set of specifiers; close detaches it. The essential idiom reserves units with newunit= so you never collide with another open file or the preconnected ones:
program open_demo
implicit none
integer :: unit
real :: x = 2.5
open (newunit=unit, file="output.txt", status="replace", action="write")
write (unit, '(a,f6.2)') "value = ", x
close (unit) ! flush and release
end program open_demo
Key open specifiers: status="new" (must not exist), "replace" (overwrite), "old" (must exist), "scratch" (temp file, deleted on close); action="read|write|readwrite" declares the file's role; form="formatted|unformatted" selects text or binary; access="sequential|direct|stream" selects the access mode (default sequential). For historical Windows/Intel portability add convert="big_endian" where files cross architectures.
Formatted output: the FORMAT language
The (...) descriptors control every character of output. The four you will use daily: i0/iw integers (with or without fixed width), f.d fixed-point reals (f8.3), e.d scientific (e12.5), a text, plus 1x/nx spacing and / newlines. A format repeats to fit the data list, and a trailing i0 never truncates. Use a literal in the statement, or an explicit 100 format (...) label for complex layouts.
Reading data and robust error handling
read consumes records into variables. List-directed input (read (unit, *) a, b, c) splits on whitespace and skips blank lines, while a format gives byte-level control. Structured errors: append iostat=ios and iomsg=msg to any I/O statement; a nonzero ios signals failure and msg explains it. The end=/eor= labels handle end-of-file and record boundaries gracefully — a loop over an unknown-length file is the classic pattern:
program read_all
implicit none
integer :: unit, ios, n = 0
real :: x
character(len=128) :: msg
open (newunit=unit, file="numbers.txt", status="old", action="read", &
iostat=ios, iomsg=msg)
if (ios /= 0) then
print '(a)', trim(msg) ! e.g. "No such file"
stop 1
end if
do
read (unit, *, iostat=ios) x ! ios < 0 at end of file
if (ios < 0) exit
if (ios > 0) cycle ! skip malformed records politely
n = n + 1
end do
close (unit)
print '(a,i0,a)', "read ", n, " numbers"
end program read_all
NAMELIST: config files without a parser
namelist /settings/ a, b, c declares a group of variables, then read (unit, nml=settings) reads a file of name = value lines — with one name per line, comments via !, and any order. The same statement writes the group back out self-describing. This is the configuration format of scientific Fortran; nothing ships a parser because Fortran ships the feature:
program config_demo
implicit none
integer :: unit
integer :: iterations = 100
real :: tolerance = 1.0e-6
logical :: verbose = .false.
namelist /settings/ iterations, tolerance, verbose
open (newunit=unit, file="run.nml", status="replace")
write (unit, nml=settings) ! writes the whole group, name = value
close (unit)
iterations = 0 ! prove reading restores values
open (newunit=unit, file="run.nml", status="old")
read (unit, nml=settings)
close (unit)
print *, iterations, tolerance, verbose
end program config_demo
Access modes: sequential, direct, stream
Sequential (default): records are read in order — right for logs and CSVs. Direct (access="direct", recl=): every record is at a fixed, numbered position, readable in any order — right for structured grids and database-like files. Stream (access="stream"): the file is one long byte sequence with positional seeks via pos= — right for binary blocks and files shared with C. The choice is a design decision at open time, and changing it later means rewriting the read logic; pick based on whether your consumer reads front-to-back, by index, or by byte position.
newunit= everywhere; close every unit you open; treat iostat as a mandatory part of every read/write that can fail.
program fmt
implicit none
real :: pi = 3.14159265358979
integer :: year = 2026
! f8.4 = 8 wide, 4 decimals; 1x = one space; i0 = minimal integer
print '(a,f8.4,1x,a,i0)', "pi ~", pi, "year", year
print '(f12.6,e12.5)', pi, pi ! fixed-point then scientific
end program fmt