Fortran Strings
//, 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
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'.