Skip to content

Files and Paths ​

Reading ​

f90
program read_a_file
!< Read a whole file.
use stringifor
implicit none
type(string), allocatable :: lines(:)
type(string)              :: content

call read_file(file='notes.txt', lines=lines)     ! one string for each line
print '(I0,A)', size(lines), ' lines, the last is "'//lines(size(lines))//'"'

call content%read_file(file='notes.txt')          ! the whole file in one string
print '(I0,A,I0,A)', content%len(), ' characters, ', content%count(new_line('a')), ' line ends'
endprogram read_a_file
$ readfile
3 lines, the last is "third line"
34 characters, 3 line ends
ProcedureWhat it reads
call read_file(file, lines[, form][, iostat][, iomsg])a whole file, into an allocatable array with one string for each line
call read_lines(unit, lines[, form][, iostat][, iomsg])the same, from a connected unit
call s%read_file(file[, is_fast][, form][, iostat][, iomsg])a whole file into one string, line ends included
call s%read_lines(unit[, form][, iostat][, iomsg])the same, from a connected unit
call s%read_line(unit[, form][, iostat][, iomsg])one line from a connected unit

The first two are procedures of the module, the others methods. is_fast=.true. reads the file as a stream, in one read.

Line by line:

f90
program read_line_by_line
!< Read a file one line at a time.
use stringifor
implicit none
type(string) :: line
integer      :: unit, iostat, n

n = 0
open(newunit=unit, file='poem.txt', status='old', action='read')
do
  call line%read_line(unit=unit, iostat=iostat)
  if (iostat /= 0) exit                           ! the end of the file, or an error
  n = n + 1
  print '(I0,A)', n, ': ['//line//']'
enddo
close(unit)
endprogram read_line_by_line
$ readline
1: [roses are red]
2: []
3: [violets are blue]

iostat is zero when a line has been read, an empty one included; at the end of the file it is the end-of-file code (is_iostat_end(iostat) is true) and the string is left unchanged. A last line without a line terminator is read like the others.

Writing ​

f90
program write_a_file
!< Write strings to a file.
use stringifor
implicit none
type(string) :: lines(3)

lines(1) = 'bread'
lines(2) = 'milk'
lines(3) = 'coffee'
call write_file(file='shopping.txt', lines=lines)
print '(A)', 'written'
endprogram write_a_file
$ cat shopping.txt
bread
milk
coffee
ProcedureWhat it writes
call write_file(file, lines[, form][, iostat][, iomsg])an array of strings, one for each line
call write_lines(unit, lines[, form][, iostat][, iomsg])the same, to a connected unit
call s%write_file(file[, form][, iostat][, iomsg])one string, as it is
call s%write_line(unit[, form][, iostat][, iomsg])one string to a connected unit, as one line
call s%write_lines(unit[, form][, iostat][, iomsg])one string to a connected unit, each of the lines it contains as a line

Through a unit already open:

f90
program unit_io
!< Lines written to and read from a unit already open.
use stringifor
implicit none
type(string)              :: line, text
type(string), allocatable :: lines(:)
integer                   :: unit, l

open(newunit=unit, file='notes.txt', status='replace')
line = 'first line'
call line%write_line(unit=unit)                       ! one string, one line
text = 'second line'//new_line('a')//'third line'
call text%write_lines(unit=unit)                      ! one string, a line for each line it holds
call read_lines(unit=unit, lines=lines)               ! rewinds the unit, one string for each line
close(unit, status='delete')
do l = 1, size(lines)
  print '(I0,A)', l, ': '//lines(l)
enddo
endprogram unit_io
$ unit_io
1: first line
2: second line
3: third line

Unformatted files ​

Every procedure accepts form='unformatted': the file is then read or written as a stream (access='stream'), the lines separated by the new-line character.

fortran
call read_file(file='data.bin', lines=lines, form='unformatted')
call write_file(file='data.bin', lines=lines, form='unformatted')

Paths ​

f90
program path_pieces
!< The pieces of a file name.
use stringifor
implicit none
type(string) :: path

path = '/home/user/data/archive.tar.gz'
print '(A)', path%basedir()//''
print '(A)', path%basename()//''
print '(A)', path%extension()//''
print '(A)', path%basename(strip_last_extension=.true.)//''
print '(A)', path%basename(extension='.tar.gz')//''
endprogram path_pieces
$ paths
/home/user/data
archive.tar.gz
.gz
archive.tar
archive
MethodResult
basedir([sep])the directory of a path
basename([sep][, extension][, strip_last_extension])the file name, optionally without an extension
extension()the last extension, with its dot

sep is the separator of the directories, / by default. A file name without a directory has an empty basedir.

Listing files ​

f90
program list_files
!< The files matching a pattern.
use stringifor
implicit none
type(string)              :: s
type(string), allocatable :: files(:)
integer                   :: f

call s%glob(pattern='data/*.csv', list=files)
do f = 1, size(files)
  print '(A)', files(f)//''
enddo
endprogram list_files
$ glob
data/cars.csv

glob(pattern, list) returns the paths matching a pattern, with the rules of the shell; list is an allocatable array of strings or of deferred-length characters, allocated with zero size when nothing matches. It is also a procedure of the module, call glob(self, pattern, list).

Only the wildcards *, ? and [...] are special: any other character of the pattern, a space, a ;, a $, a quote or a leading -, is part of the names searched, never a command of the shell. A matching directory is listed itself, not its content.

WARNING

glob runs the ls command: it works on Unix-like systems only.

Matching names ​

f90
program wildcards
!< The names matching a wildcard pattern.
use stringifor
implicit none
type(string) :: names(5)
integer      :: n

names(1) = 'report-2025.csv'
names(2) = 'report-2026.csv'
names(3) = 'report-2026.txt'
names(4) = 'notes.txt'
names(5) = 'Report-2026.csv'
do n = 1, size(names)
  if (names(n)%match('report-202[5-9].csv')) print '(A)', names(n)//''
enddo
print '(5L2)', names%match('*.txt')
endprogram wildcards
$ wildcards
report-2025.csv
report-2026.csv
 F F T T F

match(pattern) tells whether the whole string matches a wildcard pattern, with no file system involved: it works on any system, on names already in memory. The wildcards are the ones of the shell, as Python fnmatch.fnmatchcase: * any sequence of characters, ? any single character, [seq] a character of seq (a-z is a range), [!seq] a character not in seq. The match is case-sensitive, there is no escape character (match a wildcard with a class, [*]) and a leading dot is not special. match is elemental: on an array of strings it gives an array of logicals.

Temporary names ​

f90
program temporary_name
!< A name for a temporary file.
use stringifor
implicit none
type(string) :: s, name

name = s%tempname(prefix='scratch-')              ! a name not used by any file of the directory
print '(L1,1X,L1)', name%start_with('scratch-'), name%len() > len('scratch-')
endprogram temporary_name
$ tempname
T T

tempname([is_file][, prefix][, path]) returns a name that no file (or, with is_file=.false., no directory) of path has, starting with prefix. The name changes at each call: the example prints only what does not.

Encoding ​

f90
program base64
!< Encode and decode in base64.
use stringifor
implicit none
type(string) :: s, code

s = 'Hello World'
code = s%encode(codec='base64')
print '(A)', code//''
print '(A)', code%decode(codec='base64')//''
endprogram base64
$ encode_base64
SGVsbG8gV29ybGQ=
Hello World

encode(codec) and decode(codec) take the name of the codec; base64 is the only one available. The decoded string has exactly the encoded length: its blanks are preserved, and the padding of the code can be omitted.

The name of the codec is not case sensitive. With an unknown codec the result is a not allocated string: check it with is_allocated().

Colours for the terminal ​

f90
program colours
!< Colours and styles for the terminal.
use stringifor
implicit none
type(string) :: s

s = 'error: the file is missing'
print '(A)', s%colorize(color_fg='red', style='bold_on')
s = 'warning: the file is empty'
print '(A)', s%colorize(color_fg='yellow')
s = 'done'
print '(A)', s%colorize(color_fg='green', style='underline_on')
endprogram colours

three coloured lines in a terminal

colorize([color_fg][, color_bg][, style]) returns a character: the string wrapped in the ANSI escape codes of the colours and of the style, whose names are the ones of FACE (red, green, yellow_intense, ...; bold_on, italics_on, underline_on, ...).