Appendix F: I/O and Format Reference

A compact, hand-verified reference to Fortran input/output and the format language. It is the full catalog behind the working subset introduced in Chapter 7; read that chapter for the concepts and the reasoning, and keep this page open when you need a descriptor, an open specifier, or the exact column layout a format produces.

Two conventions make the tables below read correctly:

  • In a descriptor, w is the total field width, d the number of digits after the decimal point, m a minimum digit count, and n a repeat or position count.
  • Numeric fields are right-justified and blank-filled, and a value too wide for its field is not truncated — the field fills with asterisks (****). Examples show gfortran's output; anything the standard leaves processor-dependent is flagged as such.

F.1 Edit descriptors

Data edit descriptors (each consumes one value)

Descriptor What it does Example → output
Iw Integer, right-justified in width w. i5 of 4242
Iw.m Integer with at least m digits, zero-padded on the left. i5.3 of 42042
Fw.d Fixed-point real, d decimals. f8.2 of 3.141593.14
Ew.d Scientific, normalized to 0.d…E±ee (mantissa in $[0.1, 1)$). e12.4 of 12500.00.1250E+05
ESw.d Scientific: mantissa in $[1, 10)$ — the physicist's form. es12.4 of 12500.01.2500E+04
ENw.d Engineering: exponent a multiple of 3, mantissa in $[1, 1000)$. en12.4 of 12500.012.5000E+03
Gw.d General: F-like for ordinary magnitudes, E-like otherwise; always shows d significant digits. g12.4 of 21.521.50 + 4 blanks
Dw.d Legacy double-precision scientific: identical to Ew.d but the exponent letter is D. d12.4 of 12500.00.1250D+05
A Character, in the string's exact length. a of 'cat'cat
Aw Character in width w: right-justified if w > len, else the leftmost w characters. a5 of 'cat'cat; a2 of 'cat'ca
Lw Logical T/F, right-justified in width w. l3 of .false.F

The three scientific forms above all render the same number, 12500.0; only the placement of the decimal point and the exponent differ. ES is almost always the one you want for a reported quantity. Because these outputs turn on the exact count of leading blanks, here they are again with the field edges marked — the | bars are not part of the output:

i5      of 42        ->  |   42|
i5.3    of 42        ->  |  042|
f8.2    of 3.14159   ->  |    3.14|
f8.2    of -3.14159  ->  |   -3.14|
e12.4   of 12500.0   ->  |  0.1250E+05|
es12.4  of 12500.0   ->  |  1.2500E+04|
en12.4  of 12500.0   ->  | 12.5000E+03|
g12.4   of 21.5      ->  |   21.50    |
d12.4   of 12500.0   ->  |  0.1250D+05|
a5      of 'cat'     ->  |  cat|
a2      of 'cat'     ->  |ca|
l3      of .false.   ->  |  F|

Two subtleties are worth stating outright. For Aw on output, a field narrower than the string keeps the leftmost w characters (no asterisks — character data is never flagged for overflow, only numeric data is). And Gw.d in its F-like branch prints the F-form in a field of w-4 and then four trailing blanks (the space an exponent would have used): g12.4 of 21.5 is 21.50 followed by four spaces, twelve characters in all. Below 0.1 or at or above 10**d, Gw.d switches to the Ew.d form (so g12.4 of 1.0e-4 gives 0.1000E-03).

Control edit descriptors (position text; consume no value)

Descriptor What it does Example → output
nX Emit n blanks. (The count is required by the standard; a bare X is a common extension.) '(a,3x,a)' of 'x','y'x y
Tn Tab to absolute column n. '(a,t10,a)' of 'ab','cd'ab cd
TRn Tab right n columns from here (like nX). '(a,tr3,a)' of 'x','y'x y
TLn Tab left n columns from here (can back up over, and overwrite, text already placed). (repositions; see note)
/ End the current record and start a new one (a newline). '(a,/,a)' of 'one','two'one then two
: Stop format processing immediately if the I/O list is exhausted. '(*(i0,:,", "))' of [1,2,3]1, 2, 3
'text' or "text" A string literal: output the characters verbatim. '("n = ",i0)' of 5n = 5

The colon is the tidy way to put separators between list items but not after the last one: without it, '(*(i0,", "))' of [1,2,3] trails a stray 1, 2, 3,. Tn counts absolute columns from the start of the record, so in '(a,t10,a)' the second string begins in column 10 (ab, then seven blanks, then cd).

Repeat counts and modifiers

Form What it does Example → output
r before a descriptor Repeat that descriptor r times. '(3i4)' of 1,2,31 2 3
r(...) Repeat a whole group r times. '(2("[",i0,"]"))' of 7,8[7][8]
*(...) Unlimited repeat (Fortran 2008): apply the group to the rest of the list. Must be the last item in the format. '(*(f8.2))' of [1.0,22.0]1.00 22.00
SP / SS / S Print a leading + on positive numbers / suppress it / restore the processor default. '(sp,i4,i4)' of 5,7+5 +7
BN / BZ On input, treat blanks inside a numeric field as null (ignored) / as zeros. Default is BN. see below

The S-family and BN/BZ stay in force for the rest of the format (or until overridden). SS is the default, so SP is the one you actually reach for — to align signed columns. BN/BZ affect reading, not writing: a five-wide field holding 12 then three blanks reads as 12 under BN, but as 12000 under BZ (the blanks become trailing zeros) — a legacy of punched-card input, rarely wanted today.


F.2 Writing a format: three notations

A format is a parenthesized list of descriptors. You can supply it three ways; all are equivalent.

real(dp) :: x = 3.14159_dp
character(len=*), parameter :: fmt = '(f8.2)'

print '(f8.2)', x        ! 1. an inline character-literal format
print fmt,      x         ! 2. a named character constant (reusable, DRY)
write(*, 100)   x         ! 3. a labeled FORMAT statement (legacy but valid)
100 format(f8.2)
    3.14
    3.14
    3.14

Modern code prefers the inline literal or, when a format is reused, a named character constant. The labeled FORMAT statement is the FORTRAN 77 style you will meet constantly in legacy code; it behaves identically. A format held in a character variable can even be built at run time, which — together with writing to a character variable (an internal file) — is how you assemble output whose layout depends on the data; internal files are covered in Chapter 12.

Format reversion. If the I/O list has more values than the format has descriptors, the format is reused: control returns to the last complete parenthesized group, starting a new record each time. This is why '(*(f8.2))' is cleaner for a whole array — the unlimited-repeat * group extends to fit the list exactly, with no reversion surprises.


F.3 List-directed I/O

Write * where a format would go and the processor chooses the layout: print *, ..., read *, ..., and the general write(unit, *) ... / read(unit, *) .... In write(*, *) the two asterisks differ — the first is the unit (standard output), the second is the format (list-directed); print *, ... is shorthand for write(*, *) ....

print *, 'x =', 42

List-directed output writes a single leading blank at the start of each record (the one guaranteed feature — a ghost of line-printer carriage control), then each value in a processor-chosen width, separated by spaces. gfortran renders the line above roughly as ' x = 42', but the numeric field widths are not guaranteed by the standard, and a real prints at close to full precision in a width of the processor's choosing. On input, read *, a, b, c splits on spaces, commas, or line breaks and converts each token by the variable's type; a / in the input ends the read early.

Use list-directed for yourself — debugging, quick dumps, reading a few values you typed. Use a format for anyone (or anything) else, where the exact column and precision matter.


F.4 File I/O: open, close, and record positioning

Connect a file to a unit with open, transfer data with read/write, and disconnect with close. Always let the runtime pick the unit with newunit=; never hard-code a number.

Common open specifiers

Specifier Purpose Values (gfortran default in bold)
newunit=u Return a unique unused unit in integer u. (preferred over a literal unit number)
file='name' The file to connect. any path
status= Whether the file should pre-exist. 'old', 'new', 'replace', 'scratch', 'unknown'
access= Record-addressing mode. 'sequential', 'direct', 'stream'
form= Formatted (text) or unformatted (raw bytes). 'formatted' (seq.) / 'unformatted'
action= Permitted transfers. 'read', 'write', 'readwrite'
position= Where to start a sequential file. 'asis', 'rewind', 'append'
recl= Record length (required for direct; a maximum for sequential). integer; bytes in gfortran
iostat=ios / iomsg=msg Capture status and message instead of aborting. see §F.7

close(u) disconnects the unit; close(u, status='delete') also removes the file. Three statements reposition an already-open sequential file:

Statement Effect
rewind(u) Reposition to the beginning of the file.
backspace(u) Move back one record (so the next read/write reprocesses it).
endfile(u) Write an end-of-file record here, truncating anything beyond.

Access modes

Access Set with Address a record by Records Typical use
Sequential (default) reading/writing in order text lines, or marked unformatted records logs, config, tables
Direct access='direct', recl=L rec=n (any record, any order) fixed length L restart files, fixed-size databases
Stream access='stream' pos=b (byte offset) none — a pure byte stream C / Python / NumPy interop

⚠️ Portability trap: the units of recl are processor-dependent — gfortran counts bytes, some compilers count 4-byte words. Compute the value with inquire(iolength=...) (§F.6) rather than hard-coding one, or a direct-access file written by one compiler may be unreadable by another.


F.5 NAMELIST: key/value configuration files

A namelist associates a group name with a set of variables, so one statement reads or writes them all by name. It is Fortran's built-in configuration format — the scientific programmer's everyday input.

integer  :: nx, ny
real(dp) :: alpha
namelist /config/ nx, ny, alpha

read(unit,  nml=config)     ! parse a &config ... / block into the variables
write(unit, nml=config)     ! dump the whole group back out

The input file is a block delimited by &groupname and /:

&config
  nx = 100,
  ny = 100,
  alpha = 1.0e-4,
/

Matching is case-insensitive, order is irrelevant, and any name the file omits keeps its prior (default) value — so a namelist file need only list what you want to change. Arrays are written v = 1.0 2.0 3.0 or by element v(2) = 9.0, and ! begins a comment. Reading in reverse (write(..., nml=...)) is ideal for echoing the exact parameters a run used and for restart files. Always read a namelist with iostat (§F.7): a mistyped key or group name raises a nonzero status that is trivial to report and maddening to debug if ignored. The namelist output's exact spacing is processor-dependent; when you need it pretty, format it yourself with §F.1.


F.6 Unformatted, stream, and iolength

Unformatted I/O transfers a value's exact internal bytes with no text conversion — set form='unformatted', then write(u) x / read(u) x with no format. It is exact (bit-for-bit round trips), fast, and moves a whole array — or a whole derived type — in one statement. A traditional unformatted sequential file wraps each write in record markers; stream access (access='stream', form='unformatted') writes the bytes with no markers, byte-addressable via pos=, which is the form a C or Python program can read.

real(dp) :: field(1000)
integer  :: u, reclen

! Portable direct-access record length for one row of 1000 doubles:
inquire(iolength=reclen) field          ! reclen = the right recl= for THIS compiler
open(newunit=u, file='rows.dat', access='direct', form='unformatted', recl=reclen)

inquire(iolength=n) list sets n to the record length an unformatted write of that list needs, in the compiler's own recl units — the portable way to size a direct-access recl. One honest caveat: raw unformatted and stream files are not self-describing and not portable — they record nothing about shape, type, or byte order (endianness). For data you must share, archive, or read years later, use a self-describing format (HDF5 or NetCDF, Chapter 25); for visualization, the VTK output of Chapter 26.


F.7 I/O error handling

Add iostat= and iomsg= to any I/O statement to turn a fatal error into a value you can act on.

Specifier Meaning
iostat=ios Integer status: 0 = success; negative = end-of-file/record; positive = an error.
iomsg=msg Fills a character variable with a human-readable description of the failure.

Because the exact positive/negative values are processor-defined, compare against the named constants from the intrinsic module iso_fortran_env, never against literals like -1:

Constant (use iso_fortran_env) Returned by iostat when…
iostat_end the read reached end of file
iostat_eor a non-advancing read (advance='no') reached end of record
use, intrinsic :: iso_fortran_env, only: iostat_end
integer :: ios
do
   read(u, *, iostat=ios) x
   if (ios == iostat_end) exit        ! clean end of file
   if (ios /= 0) then                 ! any other nonzero: a genuine error
      print '(a, i0)', 'read error, iostat = ', ios
      exit
   end if
   ! ... process x ...
end do

The two habits worth building: guard every open with iostat/iomsg and report trim(msg), and loop reads until iostat == iostat_end. (Older code branches on end=label and err=label instead; iostat is the modern, structured equivalent.)


F.8 Common format gotchas

Symptom Cause Fix
A field prints as **** The value is wider than w (Fortran refuses to truncate a number). Widen w, or use es/g for values of unpredictable magnitude.
Read-back value differs from what you wrote Text formatting is lossyf8.2 keeps two decimals of a ~15-digit double. For exact round trips, use unformatted or stream I/O (§F.6).
A stray leading space / misaligned first column List-directed output emits one leading blank. Use an explicit format, e.g. print '(a)', s.
E vs ES shows an unexpected mantissa Ew.d normalizes to 0.d…; ESw.d to 19.d…. Pick the one you mean; es for reported quantities.
A direct-access file is unreadable on another system recl units differ (bytes vs. words) or byte order differs. Size recl with inquire(iolength=); for portability use HDF5/NetCDF (§F.6).
A namelist parameter is silently ignored, or the read fails The file's group name or a key does not match the declared group. Read with iostat, check iomsg; verify names (matching is case-insensitive but must otherwise be exact).
A list-directed real looks ugly, or differs between compilers The processor chooses width and precision for list-directed output. Format it yourself with f/es (§F.1).