ZetCode

Fortran environment and processes

last modified November 14, 2026

A program does not run in a vacuum. The shell that started it passes a list of environment variables, each a name and a value, and the operating system lets it start other programs and wait for them to finish. Both are part of what Fortran calls the execution environment, and both are reachable through intrinsics: get_environment_variable reads one name, and execute_command_line runs a shell command. The module iso_fortran_env completes the picture with the version of the compiler, the kinds it supports and the numbers of the standard input and output units.

This article reads variables and supplies defaults when they are missing, investigates the padded-name option, runs shell commands and collects their exit statuses, prints the compiler version and the unit numbers, distinguishes standard output from standard error, uses the shell to prepare a file and then reads it in Fortran, and splits the PATH variable into its directories.

Reading environment variables

call get_environment_variable(name, value, length, status) copies the value of the variable name into value. The status follows a convention that is worth remembering: zero means the variable exists and its value fitted, a positive value means it does not exist, and a negative value means the value was longer than the variable and had to be truncated. length reports the full length in every case, so a negative status still tells the program how much room it needs.

e1.f90
program e1
    implicit none
    character(len=512) :: value
    integer :: length, status

    call get_environment_variable('HOME', value, length, status)
    print '(a, i0)', 'status: ', status
    print '(a, i0)', 'length: ', length
    print '(a, a)', 'home:   ', value(1:length)

    call get_environment_variable('SHELL', value, length, status)
    print '(a, a)', 'shell:  ', value(1:length)

    call get_environment_variable('PATH', value, length, status)
    print '(a, i0, a, i0)', 'the path is ', length, ' characters long, &
        &status ', status

    call get_environment_variable('F2026_SURELY_MISSING', value, length, status)
    print '(a, i0)', 'a missing variable gives status ', status
    print '(a, i0)', 'and length ', length
end program e1

HOME is ten characters and SHELL is nine, and both fit the buffer. PATH does not: its value is 1353 characters, the call returns -1 and the variable holds only the first 512, which is why the program prints the length and not the text. A missing name returns status 1 and length zero, so the test is on the status rather than on the text, since an existing variable may be empty. The exact lengths depend on the machine, but the three cases always look like this.

$ gfortran e1.f90 -o e1
$ ./e1
status: 0
length: 10
home:   /home/jano
shell:  /bin/bash
the path is 1353 characters long, status -1
a missing variable gives status 1
and length 0

Defaults and padded names

Two details make the function pleasant to use. The first is the default: set value before the call and, if the status says the variable is missing, keep that text. The second is the fifth argument, trim_name. When it is true, which is the default, trailing blanks in the name are ignored, so 'HOME    ' still finds HOME; when it is false, the blanks are part of the name and nothing is found. The variable can also be set for a single run by writing the assignment in front of the command.

e2.f90
program e2
    implicit none
    character(len=64) :: value
    integer :: length, status

    call get_environment_variable('F2026_DEMO', value, length, status)
    if (status /= 0) then
        value = 'not set'
        length = len_trim(value)
    end if
    print '(a, i0, a, a)', 'demo: status ', status, ', value ', value(1:length)

    value = 'fallback'
    call get_environment_variable('F2026_MISSING', value, length, status)
    if (status /= 0) then
        value = 'fallback'
        length = len_trim(value)
    end if
    print '(a, i0, a, a)', 'missing: status ', status, ', value ', value(1:length)

    call get_environment_variable('HOME        ', value, length, status, .true.)
    print '(a, i0, a, a)', 'padded name, trimmed: status ', status, &
        ', value ', value(1:length)

    call get_environment_variable('HOME        ', value, length, status, .false.)
    print '(a, i0)', 'padded name, not trimmed: status ', status
end program e2

The first line is run twice, once with F2026_DEMO=hello in front of the command and once without it. With the assignment the variable is set for that process only, the status is zero and the value is hello; without it the status is 1 and the fallback text appears. The missing variable takes the same path. The two lines with a padded name differ only in the fifth argument, and the second of them reports status 1, which is exactly what the option is for.

$ gfortran e2.f90 -o e2
$ F2026_DEMO=hello ./e2
demo: status 0, value hello
missing: status 1, value fallback
padded name, trimmed: status 0, value /home/jano
padded name, not trimmed: status 1
$ ./e2
demo: status 1, value not set
missing: status 1, value fallback
padded name, trimmed: status 0, value /home/jano
padded name, not trimmed: status 1

Running another program

execute_command_line passes its string to the system shell, which means the full syntax of the shell is available: pipes, redirection, wildcards and several commands separated by semicolons. The call waits for the command to finish unless wait is false, and exitstat receives the exit status of the command, the same number the shell sees as $?. cmdstat reports whether the command could be started at all, and cmdmsg explains a failure in words.

e3.f90
program e3
    implicit none
    character(len=256) :: cmdmsg
    integer :: exitstat, cmdstat

    call execute_command_line('echo hello from the shell', exitstat=exitstat, &
        cmdstat=cmdstat, cmdmsg=cmdmsg)
    print '(a, i0, a, i0)', 'exit status ', exitstat, ', command status ', &
        cmdstat

    call execute_command_line('pwd', exitstat=exitstat)
    print '(a, i0)', 'pwd returns ', exitstat

    call execute_command_line('exit 3', exitstat=exitstat)
    print '(a, i0)', 'exit 3 returns ', exitstat

    call execute_command_line('nosuchcommand2026', exitstat=exitstat, &
        cmdstat=cmdstat)
    print '(a, i0, a, i0)', 'a missing command: exit ', exitstat, &
        ', command status ', cmdstat
end program e3

The echo prints its line directly on the terminal, because the child process inherits the standard output of the parent. pwd returns zero, exit 3 returns three, and the missing command returns 127, the shell's code for "command not found", while cmdstat is three, meaning the command line itself was invalid. The last call is the reason to always pass cmdstat: gfortran terminates the program when a command cannot be executed and cmdstat is absent, and only with the argument present does the program get the chance to continue.

$ gfortran e3.f90 -o e3
$ ./e3
hello from the shell
exit status 0, command status 0
/tmp/opencode/fa/proc
pwd returns 0
exit 3 returns 3
sh: 1: nosuchcommand2026: not found
a missing command: exit 127, command status 3

The compiler, the kinds and the units

iso_fortran_env is a standard module that every compiler provides. It defines the named kinds int8 to int64 and real32 to real128, the constants input_unit, output_unit and error_unit, and the negative status values iostat_end and iostat_eor. It also contains the two functions compiler_version and compiler_options, which return the version and the flags of the compiler that built the program.

e4.f90
program e4
    use iso_fortran_env
    implicit none

    print '(a, a)', 'compiler: ', compiler_version()
    print '(a, a)', 'options:  ', compiler_options()
    print '(a, i0)', 'int8 bits:     ', storage_size(0_int8)
    print '(a, i0)', 'int16 bits:    ', storage_size(0_int16)
    print '(a, i0)', 'int32 bits:    ', storage_size(0_int32)
    print '(a, i0)', 'int64 bits:    ', storage_size(0_int64)
    print '(a, i0)', 'real32 bits:   ', storage_size(0.0_real32)
    print '(a, i0)', 'real64 bits:   ', storage_size(0.0_real64)
    print '(a, i0)', 'real128 bits:  ', storage_size(0.0_real128)
    print '(a, i0)', 'input unit:    ', input_unit
    print '(a, i0)', 'output unit:   ', output_unit
    print '(a, i0)', 'error unit:    ', error_unit
    print '(a, i0)', 'iostat_end:    ', iostat_end
    print '(a, i0)', 'iostat_eor:    ', iostat_eor
end program e4

storage_size returns the width of a value in bits, so the two table columns confirm that the named kinds mean what they say. The unit numbers are the traditional ones: 5 for input, 6 for output, and 0 for the error stream, which is not the same as unit 6. iostat_end is -1 and iostat_eor is -2 on this compiler. The options line repeats the flags this series uses, and the pre-include entry is added by the compiler itself.

$ gfortran e4.f90 -o e4
$ ./e4
compiler: GCC version 13.2.0
options:  -mtune=generic -march=x86-64 -fpre-include=/usr/include/finclude/math-vector-fortran.h
int8 bits:     8
int16 bits:    16
int32 bits:    32
int64 bits:    64
real32 bits:   32
real64 bits:   64
real128 bits:  128
input unit:    5
output unit:   6
error unit:    0
iostat_end:    -1
iostat_eor:    -2

Standard error and end of file

The three unit constants are ordinary integers, so they can be used wherever a unit number is expected. Writing to error_unit puts a message on the standard error stream, which a terminal shows mixed with the normal output but a redirection to a file does not capture. The negative status values are equally ordinary integers, and comparing a read status with iostat_end is how a loop distinguishes "no more data" from a real error such as a malformed number.

e5.f90
program e5
    use iso_fortran_env, only: output_unit, error_unit, iostat_end
    implicit none
    character(len=20) :: line
    integer :: a, b, ios

    line = '42'
    read (line, *, iostat=ios) a, b
    print '(a, i0, a, i0)', 'first value: ', a, ', status ', ios
    print '(a, l1)', 'that status is iostat_end: ', ios == iostat_end
    print '(a, i0)', 'the constant itself is:    ', iostat_end

    line = '7 8'
    read (line, *, iostat=ios) a, b
    print '(a, i0, a, i0, a, i0)', 'two values: ', a, ' and ', b, &
        ', status ', ios

    write (output_unit, '(a)') 'this line goes to standard output'
    write (error_unit, '(a)') 'this line goes to standard error'
end program e5

The internal file holds the single number 42, so a request for two values reads the first and reports -1 for the second, which the comparison identifies as the end of the data. The next line reads from a record with two numbers and gets both with status zero. The two write statements look identical but land on different streams: the first appears among the program's output, the second is the last line, printed by the shell's error channel.

$ gfortran e5.f90 -o e5
$ ./e5
first value: 42, status -1
that status is iostat_end: T
the constant itself is:    -1
two values: 7 and 8, status 0
this line goes to standard output
this line goes to standard error

A shell command that prepares a file

The two features meet when a program uses the shell for work that Fortran does badly, such as creating a file with a fixed content, and then reads the result with ordinary Fortran input. The command below uses printf with escaped newlines to write six numbers, one per line, and the program opens the file with newunit and reads it with list-directed input until the status turns nonzero. One number per record matters here, because a list-directed read consumes one record at a time.

e6.f90
program e6
    implicit none
    integer :: unit, ios, total, count, exitstat
    real :: x

    call execute_command_line('printf "4\n8\n15\n16\n23\n42\n" > numbers.txt', &
        exitstat=exitstat)
    print '(a, i0)', 'the shell returned ', exitstat

    open (newunit=unit, file='numbers.txt', status='old', action='read', &
        iostat=ios)
    if (ios /= 0) then
        print '(a)', 'the file was not created'
        stop 1
    end if

    total = 0
    count = 0
    do
        read (unit, *, iostat=ios) x
        if (ios /= 0) exit
        total = total + nint(x)
        count = count + 1
    end do
    close (unit)

    print '(a, i0)', 'numbers in the file: ', count
    print '(a, i0)', 'their total:         ', total
end program e6

The shell returns zero, which the program checks before it tries to open the file, and the six numbers total 108. The values 4, 8, 15, 16, 23 and 42 are the ones from the television series, chosen because they are easy to add in the head. The loop stops on any nonzero status, so a truncated file or a word in place of a number would end the total early; a careful program compares the status with iostat_end and treats anything else as an error.

$ gfortran e6.f90 -o e6
$ ./e6
the shell returned 0
numbers in the file: 6
their total:         108

Splitting a path list

PATH is a list of directories separated by colons, and turning it into an array is a small exercise in string handling that uses the environment function, an allocatable array and two passes over the text: the first counts the separators to size the array, the second copies each field. Empty fields are legal and mean the current directory, so the program prints each entry inside brackets, where an empty one is visible.

e7.f90
program e7
    implicit none
    character(len=4096) :: path
    character(len=512), allocatable :: entry(:)
    integer :: length, status, i, start, n, separators

    call get_environment_variable('PATH', path, length, status)
    if (status /= 0) then
        print '(a, i0)', 'cannot read the path, status ', status
        stop 1
    end if

    separators = 0
    do i = 1, length
        if (path(i:i) == ':') separators = separators + 1
    end do

    allocate (entry(separators + 1))
    start = 1
    n = 0
    do i = 1, length
        if (path(i:i) == ':') then
            n = n + 1
            entry(n) = path(start:i - 1)
            start = i + 1
        end if
    end do
    n = n + 1
    entry(n) = path(start:length)

    print '(a, i0)', 'path length:  ', length
    print '(a, i0)', 'directories:  ', n
    do i = 1, min(3, n)
        print '(a, i0, a, a, a)', '  ', i, ': [', trim(entry(i)), ']'
    end do
    print '(a, i0, a)', 'the longest entry has ', maxval(len_trim(entry)), &
        ' characters'
    print '(a, i0)', 'empty entries: ', count(len_trim(entry) == 0)
end program e7

This machine has 46 directories in its path, the first three of which are shown with their brackets. The longest entry is 71 characters, which sets the smallest buffer that could hold any single entry, and there are no empty fields here. The same loop splits any colon-separated list, such as the MANPATH or LD_LIBRARY_PATH variables, and the reverse operation, joining an array with colons, uses the same slicing and concatenation.

$ gfortran e7.f90 -o e7
$ ./e7
path length:  1353
directories:  46
  1: [/home/jano/perl5/perlbrew/bin]
  2: [/home/jano/perl5/perlbrew/perls/perl-5.44.0/bin]
  3: [/home/jano/.elan/bin]
the longest entry has 71 characters
empty entries: 0

Next steps

The command-line arguments article reads the words of the invocation with the same status conventions, and the files article covers the open, read and close used by the file program. The strings article explains the slicing and len_trim behind the path split, the allocatable arrays article covers the allocation sized by the separator count, the data types article describes the named kinds that iso_fortran_env provides, and the modules article explains how a module such as iso_fortran_env is used with only.

Source

Fortran 2018 standard (J3/18-007r1)

GNU Fortran manual: GET_ENVIRONMENT_VARIABLE

GNU Fortran manual: EXECUTE_COMMAND_LINE

In this article we have read environment variables and their status codes, supplied defaults for missing names, used the padded-name option, run shell commands and collected their exit statuses, printed the compiler version, options and kinds, told standard output from standard error, identified the end of an internal file, prepared a file with printf and summed its numbers in Fortran, and split the path list into an array of directories. The previous article, Fortran command-line arguments, uses the same status arguments for the words of the command line.

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.