Fortran Strings

A Fortran string is a fixed-length block of characters, or a deferred-length allocatable one. Text work uses a compact set of intrinsics — concatenate with //, normalize with trim and adjustl, slice like an array — and reads numbers from text with internal files. This lesson covers the full character toolkit.

Declarations: fixed and deferred length

character(len=n) :: s makes a fixed block of exactly n characters. Assignment pads with blanks or truncates, silently — nothing is allocated, nothing fails. When the length must match the data, declare character(len=:), allocatable :: s; then every assignment resizes the string to the right-hand side. This is the Fortran 2003 idiom that finally made text handling painless.

program string_decl
  implicit none
  character(len=8)         :: fixed   ! always exactly 8 characters
  character(len=:), allocatable :: growing
  fixed = "abc"                      ! stores "abc     " (padded)
  growing = "once upon a time"       ! exact length, allocated for it
  growing = growing // " in HPC"     ! grows again on the next assignment
  print *, "|", fixed, "|"           ! pad spaces are visible here
  print *, "|", trim(fixed), "|"     ! trailing blanks removed for output
  print '(a,i0)', "len(growing) = ", len(growing)
end program string_decl

The character intrinsics

The intrinsic list that covers 90% of real text work: len (declared length) and len_trim (length without trailing blanks); trim (remove trailing blanks — call it at every output site); adjustl/adjustr (move leading blanks right / trailing blanks left); index(s, sub) (position of the first occurrence of a substring, optionally searching back=.true., returning 0 when absent); scan and verify (positions of any, or respectively all, characters of a set); repeat(s, n) (n concatenated copies — banner rulers); achar(k)/iachar(c) (code point ↔ character).

program string_ops
  implicit none
  character(len=*), parameter :: path = "/home/user/data/run01.dat"
  integer :: slash
  slash = index(path, "/", back=.true.)   ! the LAST slash
  print *, path(slash+1:)                 ! "run01.dat" — slice to the end
  print *, repeat("-", 40)                ! a 40-dash ruler
  print *, "abc" // "def"                 ! concatenation → "abcdef"
end program string_ops

Internal files: text ↔ numbers

Fortran's most underrated text feature: read and write can target a character variable instead of a file unit. The same formatting machinery that writes files is reused, without disk — the textbook way to convert numbers to strings with controlled precision, parse CSV tokens, or build SQL/HTML strings:

program internal_files
  implicit none
  character(len=32) :: buffer
  real :: temperature = 21.437
  integer :: year = 2026

  write (buffer, '(f6.2,a,i0)') temperature, " C recorded in ", year
  print *, trim(buffer)           ! "21.44 C recorded in 2026"

  ! The reverse trip: parse a string back into numbers.
  buffer = "12.5, 7"
  read (buffer, *) temperature, year          ! list-directed read
  print *, temperature, year                  ! 12.5000000      2026
end program internal_files

Practical text processing

A realistic fragment ties the tools together: read a log line, find a key, extract its value, convert it. Splitting on a delimiter is a index loop; padding to a fixed column is adjustr plus repeat. For heavier parsing, the stdlib string modules (stdlib_strings: slice, strip, ends_with...) provide the conveniences the standard leaves out — see the ecosystem lesson.

program parse_line
  implicit none
  character(len=*), parameter :: line = "temp=21.437 align=x"
  character(len=32) :: rest, value
  integer :: eq
  real :: temp
  eq = index(line, "=")          ! position of the first '='
  rest = line(eq+1:)             ! everything after it
  eq = index(rest, " ")          ! next space ends the value token
  if (eq == 0) eq = len(rest) + 1
  value = rest(:eq-1)            ! "21.437"
  read (value, *) temp           ! parse the number
  print '(a,f7.3)', "parsed temperature = ", temp
end program parse_line
Note on case handling. The standard has no case-conversion intrinsic (to_upper lives in stdlib and common extensions); compare text with </> works lexicographically on code points, and case-insensitive matching normally normalizes to lowercase yourself with achar(iachar(c)+32) for ASCII 'A'..'Z'.