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.
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.
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.
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.
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.
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.
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.
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
List all Fortran tutorials.