ZetCode

Fortran command-line arguments

last modified November 14, 2026

A program that reads its settings from the command line is easier to reuse than one that hard-codes them: the same binary can process any file, repeat a task any number of times or run in a verbose mode. The shell splits the line into words, removes the quotes, and passes the result to the program as an array of strings, one string per argument. Fortran does not expose that array directly; two intrinsics read it, command_argument_count for the number of arguments and get_command_argument for the text of one of them.

This article reads the arguments, converts them to numbers with iostat checks, writes a small option parser, produces usage messages and meaningful exit codes, skips arguments that do not parse, investigates how the shell treats spaces and quotes, and shows what happens when the receiving variable is too short. The programs are run with arguments, and the outputs below show the exact command lines that produced them.

Counting and reading the arguments

command_argument_count() returns the number of arguments, not counting the program name, and call get_command_argument(i, value) copies the i-th one into a character variable. Two optional arguments add information: length receives the real length of the argument, which may be larger than the variable, and status receives zero on success. A third intrinsic, get_command, returns the whole line including the program name and the spacing, exactly as the shell passed it.

a1.f90
program a1
    implicit none
    character(len=80) :: arg, whole
    integer :: i, count, length, status

    count = command_argument_count()
    print '(a, i0)', 'argument count: ', count

    do i = 1, count
        call get_command_argument(i, arg, length, status)
        print '(i2, a, a, a, i0, a, i0)', i, '  "', arg(1:length), &
            '"  length ', length, ', status ', status
    end do

    call get_command(whole)
    print '(a, a)', 'the whole command: ', trim(whole)
end program a1

Three arguments arrive from the line ./a1 alpha beta 7: the words are split at the spaces and the quotation marks are gone. One important difference from C is that the program name is not an element of the argument list, so the loop starts at one and ends at the count, and the name appears only in the string returned by get_command. The length reported for each argument is its length as the shell passed it, without the padding that the fixed-length variable adds.

$ gfortran a1.f90 -o a1
$ ./a1 alpha beta 7
argument count: 3
 1  "alpha"  length 5, status 0
 2  "beta"  length 4, status 0
 3  "7"  length 1, status 0
the whole command: ./a1 alpha beta 7
$ ./a1
argument count: 0
the whole command: ./a1

Turning text into numbers

Every argument arrives as text, even one that looks like a number. The internal file read read(arg, *, iostat=status) x applies the same rules as a read from the keyboard: leading spaces are ignored, a decimal point makes the value real, and anything else sets status to a nonzero value instead of stopping the program. The same text can be read twice, once into a real and once into an integer, and the two attempts may disagree, which is how a program distinguishes 42 from 2.5.

a2.f90
program a2
    implicit none
    character(len=32) :: arg
    integer :: n, status
    real :: x

    call get_command_argument(1, arg, status=status)
    if (status /= 0) then
        print '(a)', 'no argument was given'
        stop
    end if

    read (arg, *, iostat=status) x
    if (status == 0) then
        print '(a, f0.4)', 'as a real:      ', x
        print '(a, es10.3)', 'squared:        ', x * x
    else
        print '(a, a, a)', '"', trim(arg), '" is not a real number'
    end if

    read (arg, *, iostat=status) n
    if (status == 0) then
        print '(a, i0)', 'as an integer:  ', n
        print '(a, i0)', 'doubled:        ', 2 * n
    else
        print '(a, a, a)', '"', trim(arg), '" is not an integer'
    end if
end program a2

The integer 42 converts to both types and is doubled; 2.5 converts only to a real, and the integer attempt fails as it should; the word hello fails both times. The last run has no argument at all, which get_command_argument reports through its status rather than by returning an empty string, so the program can tell a missing argument from an empty one. Checking the status after every conversion is what keeps a typo on the command line from turning into a silent zero.

$ gfortran a2.f90 -o a2
$ ./a2 42
as a real:      42.0000
squared:         1.764E+03
as an integer:  42
doubled:        84
$ ./a2 2.5
as a real:      2.5000
squared:         6.250E+00
"2.5" is not an integer
$ ./a2 hello
"hello" is not a real number
"hello" is not an integer
$ ./a2
no argument was given

A small option parser

Options are conventions, not a language feature. The usual convention is that an argument beginning with a hyphen is an option, a bare word is a positional argument, and an option that takes a value consumes the next argument. The parser below walks the list with an index it can advance by two, handles each option in a select case, and stores the result in ordinary variables with defaults set before the loop.

a3.f90
program a3
    implicit none
    character(len=64) :: arg, name
    integer :: i, count, n, status
    logical :: upper

    n = 1
    upper = .false.
    name = 'world'
    count = command_argument_count()

    i = 1
    do while (i <= count)
        call get_command_argument(i, arg)
        select case (trim(arg))
        case ('-n')
            i = i + 1
            call get_command_argument(i, arg)
            read (arg, *, iostat=status) n
            if (status /= 0 .or. n < 1 .or. n > 10) then
                print '(a)', 'the -n option needs a number from 1 to 10'
                stop 2
            end if
        case ('-u')
            upper = .true.
        case default
            if (arg(1:1) == '-') then
                print '(a, a)', 'unknown option: ', trim(arg)
                stop 2
            end if
            name = arg
        end select
        i = i + 1
    end do

    if (upper) name = to_upper(trim(name))
    do i = 1, n
        print '(a, i0, a, a, a)', 'hello ', i, ', ', trim(name), '!'
    end do
    if (n > 1) print '(a, i0, a)', 'that was ', n, ' greetings'
contains
    function to_upper(text) result(out)
        character(len=*), intent(in) :: text
        character(len=len(text)) :: out
        integer :: k

        out = text
        do k = 1, len(text)
            if (out(k:k) >= 'a' .and. out(k:k) <= 'z') then
                out(k:k) = achar(iachar(out(k:k)) - 32)
            end if
        end do
    end function to_upper
end program a3

With no arguments the program uses its defaults: one greeting to the world. The second run sets the count to three with -n 3, turns on the uppercase conversion with -u, and supplies the name as a positional argument. The third run shows that -n 2 keeps the default name, because no positional argument followed it. An unknown option prints a message and stops with status 2, which is the conventional code for a usage error.

$ gfortran a3.f90 -o a3
$ ./a3
hello 1, world!
$ ./a3 -n 3 -u fortran
hello 1, FORTRAN!
hello 2, FORTRAN!
hello 3, FORTRAN!
that was 3 greetings
$ ./a3 -n 2
hello 1, world!
hello 2, world!
that was 2 greetings
$ ./a3 -x; echo "exit status: $?"
unknown option: -x
STOP 2
exit status: 2

Usage messages and exit status

A program that cannot continue should say why on the standard error stream, not on standard output, because the output may be redirected to a file while the complaint must still reach the terminal. The constant error_unit from iso_fortran_env is that stream. The number passed to stop becomes the exit status seen by the shell: zero means success, and small nonzero values have conventional meanings, 1 for a runtime failure and 2 for bad usage.

a4.f90
program a4
    use iso_fortran_env, only: error_unit
    implicit none
    character(len=256) :: path, line
    integer :: lines, unit, ios

    if (command_argument_count() /= 1) then
        write (error_unit, '(a)') 'usage: a4 FILE'
        stop 2
    end if

    call get_command_argument(1, path)
    open (newunit=unit, file=trim(path), status='old', action='read', &
        iostat=ios)
    if (ios /= 0) then
        write (error_unit, '(a, a)') 'cannot open ', trim(path)
        stop 1
    end if

    lines = 0
    do
        read (unit, '(a)', iostat=ios) line
        if (ios /= 0) exit
        lines = lines + 1
    end do
    close (unit)

    print '(a, a)', 'file:  ', trim(path)
    print '(a, i0)', 'lines: ', lines
end program a4

The program needs exactly one argument, so the count is compared with one before anything is read. With a file name it opens the file, counts the lines until the read fails, and prints the total; with no argument it prints the usage line and exits with 2; with a name that does not exist the open fails and it exits with 1. The shell command echo "exit status: $?" prints the status of the preceding command, and the values 2 and 1 in the transcript are the ones the program chose.

$ gfortran a4.f90 -o a4
$ printf 'alpha\nbeta\ngamma\n' > words.txt
$ ./a4 words.txt
file:  words.txt
lines: 3
$ ./a4; echo "exit status: $?"
usage: a4 FILE
STOP 2
exit status: 2
$ ./a4 missing.txt; echo "exit status: $?"
cannot open missing.txt
STOP 1
exit status: 1

Skipping arguments that do not parse

Not every bad argument deserves a fatal error. A tool that adds numbers can ignore a word it cannot convert, report it and continue with the rest, which is friendlier when the arguments come from a script that may pass an occasional comment. The pattern is the same iostat test, with the failure branch printing a warning instead of stopping.

a5.f90
program a5
    implicit none
    character(len=32) :: arg
    integer :: i, count, value, total, good, status

    count = command_argument_count()
    total = 0
    good = 0
    do i = 1, count
        call get_command_argument(i, arg)
        read (arg, *, iostat=status) value
        if (status == 0) then
            total = total + value
            good = good + 1
        else
            print '(a, a, a)', 'skipping "', trim(arg), '", not an integer'
        end if
    end do
    print '(a, i0)', 'numbers read: ', good
    print '(a, i0)', 'their sum:    ', total
    if (count == 0) print '(a)', 'usage: a5 NUMBER [NUMBER ...]'
end program a5

The first run adds five numbers to 15. The second run meets the word x, reports it, and still adds the other four values, including the negative one, which the read accepts without complaint. The count is of the numbers that were accepted, not of the arguments that were given, and the last run with no arguments prints the usage line after the two totals, since a program that prints a summary first and a hint afterwards reads better than one that produces nothing at all.

$ gfortran a5.f90 -o a5
$ ./a5 1 2 3 4 5
numbers read: 5
their sum:    15
$ ./a5 10 -4 7 x 3
skipping "x", not an integer
numbers read: 4
their sum:    16
$ ./a5
numbers read: 0
their sum:    0
usage: a5 NUMBER [NUMBER ...]

Spaces, quotes and empty arguments

The shell, not Fortran, decides where one argument ends and the next begins. Double or single quotes keep a space inside one argument, a quoted empty string becomes an argument of length zero, and the quotes themselves disappear before the program ever sees the text. This matters for a program that builds a command for another program, because the quoting has to be added back.

a6.f90
program a6
    implicit none
    character(len=128) :: arg
    integer :: i, count, length, status, total

    count = command_argument_count()
    total = 0
    print '(a, i0)', 'arguments: ', count
    do i = 1, count
        call get_command_argument(i, arg, length, status)
        total = total + length
        print '(i2, a, a, a, i0, a, i0)', i, '  [', arg(1:length), &
            ']  length ', length, ', status ', status
    end do
    print '(a, i0)', 'characters in all: ', total
end program a6

The first argument is hello world, eleven characters including the space, and it arrives as one string rather than two. The third argument is empty: its length is zero, its status is zero, and the bracket pair with nothing between them is how the program shows that the argument exists. The total of seventeen characters is the sum of the three lengths.

$ gfortran a6.f90 -o a6
$ ./a6 "hello world" second ""
arguments: 3
 1  [hello world]  length 11, status 0
 2  [second]  length 6, status 0
 3  []  length 0, status 0
characters in all: 17
$ ./a6
arguments: 0
characters in all: 0

Truncation and the whole command line

A fixed-length variable may be shorter than the argument the user typed. In that case get_command_argument copies what fits and sets status to a negative value, which is the signal that the text was truncated. The optional length argument still reports the full length, so a program can allocate a wider buffer, or refuse the argument, or simply warn. Concatenating the pieces of several arguments with a space between them rebuilds a command line for a shell, exactly as get_command would return it.

a7.f90
program a7
    implicit none
    character(len=8) :: small
    character(len=64) :: arg, joined
    integer :: i, count, length, status, used

    call get_command_argument(1, small, length, status)
    print '(a, a, a)', 'the first eight characters: "', small, '"'
    print '(a, i0)', 'the real length:            ', length
    print '(a, i0)', 'status with an eight-char buffer: ', status

    call get_command_argument(1, arg, length, status)
    print '(a, a, a)', 'with a wider buffer: "', arg(1:length), '"'
    print '(a, i0)', 'status now: ', status

    count = command_argument_count()
    joined = ''
    used = 0
    do i = 1, count
        call get_command_argument(i, arg, length, status)
        if (used + length + 1 > len(joined)) exit
        joined(used + 1:used + length) = arg(1:length)
        used = used + length
        if (i < count) then
            joined(used + 1:used + 1) = ' '
            used = used + 1
        end if
    end do
    print '(a, a)', 'the arguments joined: ', joined(1:used)
end program a7

The first attempt reads into eight characters and gets 01234567 with status -1, while the length is the full twenty. A wider buffer takes the whole argument and the status becomes zero, which is the test a program should use before trusting the text. The joined string at the end puts the two arguments back together with a single space, the form in which the shell split them, and a program that passes that string to another command must quote any part that still contains a space.

$ gfortran a7.f90 -o a7
$ ./a7 0123456789abcdefghij second
the first eight characters: "01234567"
the real length:            20
status with an eight-char buffer: -1
with a wider buffer: "0123456789abcdefghij"
status now: 0
the arguments joined: 0123456789abcdefghij second

Next steps

The internal files article explains the read from a character variable in more detail, and the strings article covers the slicing, concatenation and comparison used by the parser. The files article shows the open with newunit and iostat that the file-counting program uses, the procedures article explains the contains block with the uppercase function, the conditionals article covers the select case construct, and the program structure article describes the layout conventions of the source files.

Source

Fortran 2018 standard (J3/18-007r1)

GNU Fortran manual: COMMAND_ARGUMENT_COUNT

GNU Fortran manual: GET_COMMAND_ARGUMENT

In this article we have counted and read the command-line arguments, converted them to reals and integers with iostat, written an option parser with defaults, printed usage messages to error_unit and returned exit statuses 1 and 2, skipped arguments that do not parse, seen how the shell treats quotes and empty arguments, detected truncation with a negative status, and rebuilt a command line by concatenation. The previous article, Fortran date and time, reads the clock with the same care about status arguments.

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.