ZetCode

Fortran internal files

last modified November 14, 2026

The read and write statements normally talk to files on disk or to the keyboard and the screen. They can also talk to a character variable, which is then called an internal file: writing into it fills it with formatted text, and reading from it interprets that text as numbers. No file is opened and nothing leaves memory, so internal files are the standard Fortran way to convert between numbers and strings, to build a line of output piece by piece, and to split a line of input into fields.

Writing a number into a string

An internal file is used as the unit of the write statement, in the place where a file unit number usually stands. The second argument is a format: a recipe that says how the values should be laid out. In '(i5)' the letters i stand for an integer, and 5 is the width of the field reserved for it; i0 is a special width that uses exactly as many characters as the number needs. The character variable that receives the text is usually called a buffer, and it must be at least as long as the text: writing into a shorter variable is an error, which is why the buffer below is larger than any result.

n1.f90
program n1
    implicit none
    character(len=20) :: buf
    integer :: n

    n = 42
    write (buf, '(i0)') n
    print *, '[', trim(buf), '] len =', len_trim(buf)
    write (buf, '(i5)') n
    print *, '[', buf, ']'
    write (buf, *) n
    print *, '[', buf, ']'
    n = -7
    write (buf, '(i0)') n
    print *, '[', trim(buf), '] len =', len_trim(buf)
end program n1

With i0 the buffer holds the two characters 42 and the remaining eighteen stay blank, so the trimmed length is 2. With i5 the number is right-aligned in five characters, giving three blanks before the digits, and the rest of the buffer is still untouched padding. The third form, write (buf, *), is list-directed like print *: the compiler picks the layout, and gfortran pads the integer to a field twelve characters wide here. The last line shows that a negative number needs one more character for the minus sign.

$ gfortran n1.f90 -o n1
$ ./n1
 [42] len =           2
 [   42               ]
 [          42        ]
 [-7] len =           2

Reading a number from a string

Reading works in the opposite direction: the character variable is the unit and the values on the right receive what was found in the text. Here the format is *, the list-directed form, which lets the compiler parse the text much as it would parse input typed at the keyboard: blanks separate values, and a decimal point or an exponent is understood. The optional iostat= argument receives a status code instead of letting a bad conversion stop the program.

n2.f90
program n2
    implicit none
    character(len=20) :: buf
    real :: x
    integer :: ios

    buf = '3.25'
    x = 0.0
    read (buf, *, iostat=ios) x
    print *, 'x =', x, 'ios =', ios

    buf = '2.5e3'
    read (buf, *, iostat=ios) x
    print *, 'x =', x, 'ios =', ios

    buf = 'abc'
    read (buf, *, iostat=ios) x
    print *, 'x =', x, 'ios =', ios
end program n2

The first two conversions succeed: 3.25 stays as it is and 2.5e3 becomes 2500. The status is 0 in both cases. The third conversion fails because abc is not a number; the status is positive, 5010 on this compiler, and x keeps the value it had before the read. A positive iostat therefore means "the text did not match", while a negative one would mean "the text ran out"; both are non-zero, so testing ios /= 0 covers every failure.

$ gfortran n2.f90 -o n2
$ ./n2
 x =   3.25000000     ios =           0
 x =   2500.00000     ios =           0
 x =   2500.00000     ios =        5010

Building a line from pieces

Converting one number per statement becomes tedious when a line contains several of them. The usual pattern is to write each piece into a small buffer with i0, trim it, and append it to a growing line. The program below produces a comma-separated list of the first five squares, which is the shape of a CSV field or of a query string.

n3.f90
program n3
    implicit none
    character(len=80) :: line
    character(len=20) :: item
    integer :: i

    line = ''
    do i = 1, 5
        write (item, '(i0)') i * i
        if (i > 1) line = trim(line) // ','
        line = trim(line) // trim(item)
    end do
    print *, '[', trim(line), ']'
    print *, 'len:', len_trim(line)
end program n3

The comma is added before every item except the first, and the buffer is trimmed on both sides of every concatenation so that the twenty-character item contributes only its digits. The result is eleven characters: five numbers and four commas. Without the trims the line would soon be full of blanks and the width of the buffer would limit how many numbers fit.

$ gfortran n3.f90 -o n3
$ ./n3
 [1,4,9,16,25]
 len:          11

Parsing a comma-separated line

The reverse task is to read a line whose fields are separated by commas. Since list-directed input treats a comma as a separator, the simplest approach is to cut the line at the commas with index, copy each piece into a small string, and read that string as a number. Empty or malformed fields are skipped when their status is not zero, which makes the parser forgiving without any extra error handling.

n4.f90
program n4
    implicit none
    character(len=60) :: line
    character(len=20) :: field
    real :: total, v
    integer :: p, c, ios, count

    line = '12.5, 3.25, 7'
    total = 0.0
    count = 0
    p = 1
    do
        c = index(line(p:), ',')
        if (c == 0) then
            field = line(p:)
            read (field, *, iostat=ios) v
            if (ios == 0) then
                total = total + v
                count = count + 1
            end if
            exit
        end if
        field = line(p:p + c - 2)
        read (field, *, iostat=ios) v
        if (ios == 0) then
            total = total + v
            count = count + 1
        end if
        p = p + c
    end do
    print *, 'values:', count
    print *, 'total: ', total
end program n4

The loop handles every field before a comma, and the branch where index returns zero handles the last field, which has no comma after it. Three values are found and the total is 22.75. The position p moves to the character after the comma each time, so the next search starts at the beginning of the next field. Reading the field with * ignores the leading space that follows a comma, which is why 3.25 converts without special treatment.

$ gfortran n4.f90 -o n4
$ ./n4
 values:           3
 total:    22.7500000    

What iostat reports

The status from a read is not just "worked" or "failed". Zero means success, a negative value means that the input ended before all values were read, and a positive value means that something in the text could not be converted. With an internal file, "ended" means that the end of the character variable was reached, so reading two numbers from a string that holds one gives a negative status on the second value, which keeps its previous value.

n5.f90
program n5
    implicit none
    character(len=20) :: buf
    integer :: a, b, ios

    a = 0
    b = 0
    buf = '10 20'
    read (buf, *, iostat=ios) a, b
    print *, a, b, ios

    a = 0
    b = 0
    buf = '10'
    read (buf, *, iostat=ios) a, b
    print *, a, b, ios

    a = 0
    b = 0
    buf = '10 x'
    read (buf, *, iostat=ios) a, b
    print *, a, b, ios
end program n5

The first read fills both variables and reports 0. The second reads 10 into a and then runs out of text for b, so it reports -1 and leaves b at zero. The third reads 10 and then meets the letter x, so it reports 5010 and again leaves b alone. The distinction matters when a loop reads values until the end of a file: -1 is the normal way to stop, while a positive status deserves a message.

$ gfortran n5.f90 -o n5
$ ./n5
          10          20           0
          10           0          -1
          10           0        5010

A conversion helper

Once the conversion is wrapped in a function, the rest of the program does not have to think about buffers. The function below returns a deferred-length string, so the caller receives exactly the digits and can concatenate them into a message. This is the Fortran equivalent of a "number to string" function in other languages, and it is used later in the article on formatted output.

n6.f90
module convmod
    implicit none
contains
    pure function int_to_str(n) result(r)
        integer, intent(in) :: n
        character(len=:), allocatable :: r
        character(len=20) :: buf

        write (buf, '(i0)') n
        r = trim(buf)
    end function int_to_str
end module convmod

program n6
    use convmod
    implicit none
    integer :: i

    do i = 1, 3
        print *, 'row ' // int_to_str(i) // ': ' // int_to_str(i * i)
    end do
end program n6

The helper's result is allocated to the trimmed length, so int_to_str(2) is a one-character string and the concatenation adds no padding. The loop prints the row number and its square, and the pattern works for any integer without the caller reserving a buffer. Internal write is allowed inside a pure function because the file is a local variable and nothing outside the function is touched.

$ gfortran n6.f90 -o n6
$ ./n6
 row 1: 1
 row 2: 4
 row 3: 9

Next steps

The formatted output article explains the edit descriptors used in these formats, such as i0, f8.2 and a, in full. The strings article covers the declarations and the trimming functions, and the reading input article applies the same iostat technique to data typed at the keyboard, and the files article shows how the same statements work on a real file instead of a variable.

Source

Fortran 2018 standard (J3/18-007r1)

GNU Fortran manual

Fortran-lang: learning resources

In this article we have used character variables as files: writing numbers into them, reading numbers back, building a line from formatted pieces, splitting a comma-separated record, and reading the status that each transfer returns. The previous article, Fortran string processing, shows how to clean and cut the text before and after the conversion.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Fortran tutorials.