Fortran

Guide To Learn

There are a few different ways for a program to get input from the user. One is the so-called standard input, where the program waits for the user to enter data through the keyboard. This approach is useful when the program’s flow depends on users’ input. If you’re old enough to have grown up with old text adventures such as Colossal Cave Adventure or Zork, or if you’ve ever read a choose-your-own-adventure book, they work exactly like this:

  • The program asks you a question, and you type in the answer.
  • The program then processes the input and acts accordingly.

We explored reading from standard input in chapter 6.

Another approach is to instruct the program to read the input data from files on disk that have been prepared beforehand. We used this liberally in chapters 5, 7, and 9. This approach is necessary when the input data is much bigger than a handful of scalar parameters or character strings; for example, initial fields to a complex simulation program or satellite data streams that come in at set times of the day.

Finally, you can provide input data to the program as command-line arguments, like with many Linux and UNIX command-line programs. This approach is suitable when the input data consists of up to several parameters. A program with a command-line interface can be used as a stand-alone program or as part of a larger, scripted pipeline. Fortran provides subroutines to inquire and read data from the command line, no matter what your operating system is–Linux, Windows, or a UNIX-like system such as macOS. The key subroutine is get_command_argument, introduced by the Fortran 2003 standard. It allows you to parse the command-line arguments by position number and store their values into a character string variable. See it in action in the following listing.

Listing 10.2 Reading the date from the command line

subroutine get_date_from_cli(date)             ❶
 
  type(datetime), intent(out) :: date          ❷
  character(len=4) :: year_arg                 ❸
  character(len=2) :: month_arg, day_arg       ❸
  integer :: year, month, day                  ❹
 
  call get_command_argument(1, year_arg)       ❺
  call get_command_argument(2, month_arg)      ❺
  call get_command_argument(3, day_arg)        ❺
 
  read (year_arg, *) year                      ❻
  read (month_arg, *) month                    ❻
  read (day_arg, *) day                        ❻
 
  date = datetime(year, month, day, 0, 0, 0)   ❼
 
end subroutine get_date_from_cli

❶ A subroutine that will return date to the caller

❷ Returns a datetime instance

❸ Character strings to store command-line arguments, length 4 for year, and length 2 for month and day

❹ Integer values to convert the character strings into

❺ Reads each argument into a character string

❻ Converts each argument from character string to an integer

❼ Creates a new datetime instance from input values

This subroutine has no input arguments, and only one output argument, date. It’s a rather short subroutine; however, it uses two new concepts that we haven’t encountered so far in the book. First, we use the get_command_argument subroutine to read the arguments from the command-line interface (CLI). What’s new about this concept is that it will allow you to provide input parameters to the program at runtime, without needing to recompile the program. The first argument to get_command_argument is an integer and refers to the position of the argument on the command line, starting immediately after the program name. The second argument is a character string in which we store the value of the command-line argument. You can see the full description of this subroutine in the following sidebar.

Using the get_command_argument subroutine

get_command_argument is a built-in subroutine that queries information about command-line arguments passed to the program. It takes at least one and up to four arguments:

  • number–A non-negative integer number indicating the position of the command-line argument to query.
  • value–An optional character string in which the value of the command-line argument will be stored. If number == 0, the name of the program is stored into value.
  • length–An optional integer that will be set to the number of characters in the command-line argument.
  • status–An optional integer status. If getting the command-line argument fails, status is set to a positive number. If the argument is truncated to fit into value (second argument from this list), it’s set to -1. Otherwise (success), status is set to zero.

Note that there’s no implicit conversion of data types from the command line to our program. If you pass integers or reals to the program, get_command_argument will always receive them as character strings. You, the programmer, are responsible for explicitly converting them to the data type that you need.

We now have our CLI arguments stored into character string variables. To create a new datetime instance, we need them as integers. How do you convert a string to an integer in Fortran? This is where we encounter the second new concept–reading a value from an internal unit. This is one of the historical features of Fortran that hails from way back. See, you’d think that you could just do something like int('42') or real('3.1415925') to get an integer or a real number from a string. However, it’s not that easy. To make the conversion, you actually need to read a number from a character variable, like you’d do from a text file:

read (unit=string, fmt=fmt) var

Here, we use the read statement to convert a character string, stored in the string variable, into a numerical (integer, real, or complex) or logical variable var. The second argumentfmt, is the formatting string to be used to parse string. We’ll look into formatting strings in the next chapter in more detail, but for now a default value (*) is good enough. Thus, to convert a character string year_arg to an integer variable year, you’d write read (year_arg, *) year, and similarly for month and day arguments. If you’re feeling adventurous, you could also write a custom constructor for the datetime type that accepts character strings as input, as well as integers.

Note that this subroutine could’ve been expressed as a function, albeit not a pure one. I chose a subroutine because using a function may imply that no side effects occur. Even though get_date_from_cli doesn’t modify any other variable in our program, it’s not pure by definition because it receives information from the program’s environment.

Back to our main program, which we can now write as shown in the following listing.

Listing 10.3 Receiving a date from the CLI and printing it to screen

program countdown
  use mod_datetime, only: datetime, get_date_from_cli     ❶
  implicit none
  type(datetime) :: birthday                              ❷
  call get_date_from_cli(birthday)                        ❸
  print *, birthday                                        ❹
end program countdown

❶ Imports the class and command-line parser from the module

❷ Declares our datetime instance

❸ Parses the command-line argument

❹ Prints the datetime instance to the screen

At this point, we can import the datetime class and get_date_from_cli parser subroutine from the module, declare the datetime instance, and initialize it with user-input values. Our app is useful already! However, let’s look back at our specification from section 10.1. We need to be able to handle typical cases of invalid user input, such as not enough command-line arguments provided, or values for year, month, or day that aren’t meaningful. This will be good to tackle in an exercise, such as the one in the “Exercise 1” sidebar.

Exercise 1: Validating user input

Edit the get_date_from_cli subroutine from listing 10.2 to check for the following:

  1. Has the user passed at least three command-line arguments to the program? If not, the app should print a short usage message and stop. Hint: use command_argument_count to get the integer number of arguments provided to the program.
  2. Do each of the year, month, and day arguments have a valid value? If not, print an informative error message and stop. Note that to test for the value of the day, you’ll need to determine the number of days given the month and year values. A function to do this is found in mod_datetime.f90 in the source code repository.

The solution to this exercise is given in the “Answer key” section near the end of this chapter.

In this section, we used the built-in get_command_argument subroutine to write a simple command-line argument parser. Just using this can get you quite far, but writing a more sophisticated CLI app with many optional arguments and rich documentation can become tedious. Fortunately, there are libraries out there that you could use for this purpose. I describe my favorite in the next sidebar.

Want to write more serious CLI apps?

command_argument_count and get_command_argument are often more than enough for many simple apps. However, these procedures are low-level tools. If you need your CLI app to receive several different arguments of different types (some of them optional) and to print sophisticated use instructions on the screen, you’re better off with a library dedicated to doing exactly that. The Fortran command Line Arguments Parser (FLAP, https://github.com/szaghi/FLAP) is an easy-to-use library for building rich CLI interfaces and docstrings. It’s developed and maintained by Stefano Zaghi, an Italian physicist and a true Fortran wizard.

Reading user input

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top