Defining commandline parameters

How might I go about defining the commandline arguments, such that I don’t have to do all or nothing?

I have (at least) 2 parameters I wish to parse to the main routine; accmethod and massonly; both being values. I also currently define the output file as > dummymain.lst but wonder if I might be better served by turning this into a commandline option with a default setting.

I’m wondering if I should be considering something along this line…

dummymain accmethod=2 massonly=1 outfile='dummymain.lst'

How would I be able to identify which is which or is this simply by reading in the parameters as usual (all as strings, which is the default action of Fortran, I believe) and simply interrogating each parameter given (so, look for accmethod= within the argument itself)?

It seems to me that you expect the user to run your program from the command line. A Windows user may prefer to run with no arguments, but have a small SETTINGS windows to pop up, showing default values and clickable buttons, etc., to change those values.

In the command line version, you expect the arguments to appear in arbitrary order, and each argument has the form

 KeyWord = value

You can maintain a table containing a “dictionary”. For each keyword string, there will be a corresponding variable in the program, possibly with a default value.

At the beginning of your program, you process the command line arguments; for each, you look up the variable (or its index, if all the variables are in an array, or the relevant component of a derived type) in the dictionary, and set the corresponding variable entity to the value after the ‘=’.

Thank you. We’re running this under commandline in Windows, OsX and Linux; since the servers I will eventually run this on are Linux units.

I will explore this further and get this set up. I did think this was probably the methodology used but appreciate the direction - it helps build my confidence. :slight_smile:

There are tools to parse command lines in a Fortran program:

M_CLI2: cracks the command line when given a prototype string that looks very much like an invocation of the program, by urbanjost. A call to get_args(3f) or one of its variants is then made for each parameter name to set the variables appropriately in the program.

fArgParse: command line argument parsing for Fortran, from Goddard-Fortran-Ecosystem

FLAP: command Line Arguments Parser for poor people, by szaghi et al. A KISS pure Fortran Library for building powerful, easy-to-use, elegant command line interfaces

A namelist input file can also be considered.

1 Like

I strongly support using one of the many command line parsing libraries available, as mentioned. However, in your particular case a simple program would do exactly what you asked for except that with some compiler and shell combinations quoting a filename can require double-quoting. For example:

program dummymain
implicit none
! define arguments and their default values
integer :: accmethod=2                       ;namelist /args/ accmethod
integer :: massonly=1                        ;namelist /args/ massonly
character(len=80) :: outfile='dummymain.lst' ;namelist /args/ outfile

character(len=256) :: input(3)=[character(len=256) :: '&args','','/']
character(len=256) :: message
integer :: i, ios
! read arguments from command line
do i=1,command_argument_count()
   call get_command_argument(i,input(2))
   read(input,nml=args,iostat=ios,iomsg=message)
   if(ios.ne.0)then
      write(*,*)'ERROR:'//trim(message)
      stop 2
   endif
enddo
write(*,nml=args)
end program dummymain
dummymain accmethod=10

technically you need to pass quotes or apostrophes around a string value
and shells often strip those characters from the command line values so
to conform to standard NAMELIST usage you need to do something like

dummymain outfile='"myfile.out"'

but most if not all compilers will work with an unquoted string as long
as it does not contain spaces or special characters, so it is an
extension but this will commonly work:

dummymain outfile=file.txt

numbers work great, even arrays and user-defined types but special
characters often need quoted to protect them from shell expansion.

Your request is unusual in that most requests are for something like ULS (Unix-Like Systems) syntax or MSWIndows DOS syntax:

dummymain --outfile  'myfile.out'  --accmethod=10
dummymain /outfile myfile.out /accmethod 10

but you have asked for something very much like NAMELIST or bash or CDC NOS syntax (sans the commas).

Another method appropriate for bash/sh/fish and tcsh/csh users is to read environment variables and to call the program as part of an env(1) statement:

env outfile='file.txt'  accmethod=20 dummymain

both methods work and are quite simple; but in the case of the environment variables you also have to convert the numeric values from strings to numerics; whereas with the first method using a NAMELIST group you do not.

PS;

Why I say this is “bash-like” is because it is, users just rarely use the built-in bash keyword-name syntax. If might surprise quite a few people that given the bash shell

#!/bin/bash
echo A is $A
echo B is $B
$ A=100 >out.txt B=200 ./a

is a completely standard way to run the script and place the output in the file “out.txt” with
contents like so:

A is 100
B is 200

the world just rarely sees it used that way, and if you start your bash shell with the -k switch
the script can be called as

a  A=11 B=20

supporting exactly the syntax style in the original OP request. So it is VERY bash-like, but for some reason few users use bash that way.

3 Likes

I hadn’t replied to this as of yet because I am still piecing this all together. However, I just HAD to say a HUGE thank you!
Though the libraries seem like such a nice idea, if ever possible I do like to code it myself and love the idea of the NAMELIST concept. My example did, indeed, give a BASH feel but I am not wedded to this. So, I am going to explore your solution a little more; before then considering my options in relation to using a library. The number of people likely to use my code when finished is low (currently 2, and I am one of them! Hahahaha!), so I’m quite free on implementation (especially since I am only doing this to simplify execution and it is not an essential part of the project).

Just what I use NAMELIST from the command line for. It is great for numbers including arrays and it is then trivial to read options from a config file (the more typical use of NAMELIST) by just having a filename option on the command line as well. No type conversions are needed and to add a new parameter you just add it to the namelist. So it is a handy way to go.

Now if Fortran just had an extension to allow a NAMELIST to have other syntax like “unix-style” and "dos-styl it would be one of the few languages with built-in command line parsing out of the box.

I have added an option in my own routines to “requote” a name so it is more portable when a string. It works as-is with numeric values but if you want to add something like

mytitle="this is the plot of the stuff"

most shells turn that into this by the time your program sees it

mytitle=this is the plot of the stuff

unless you double-quote the string on the command line. To avoid that you need to make
a little function that changes the first = to =" and adds a " at the end, and turns a " into “” and then this works very nicely with strings too.

So for anyone looking at this later, it is also a good way to experiment with NAMELIST. Do
you know what the output will be if you call the following program with

   dummymain  title=' this is my title ' arr='4*10' arr=,,3

and that allocatable variables (if allocated at the time of reading) work with NAMELIST, and …?

program dummymain
implicit none
! define arguments and their default values
integer :: accmethod=2                    ; namelist /args/ accmethod
integer :: massonly=1                     ; namelist /args/ massonly
character(len=:),allocatable :: outfile   ; namelist /args/ outfile
character(len=:),allocatable :: title     ; namelist /args/ title  
real,allocatable :: arr(:)                ; namelist /args/ arr
character(len=256) :: input(3)=[character(len=256) :: '&args','','/']
character(len=256) :: message1, message2
integer :: i, ios, equal_pos
! allocate allocatable variables
outfile='input.txt'//repeat(' ',1024)
title=repeat(' ',1024)
arr=[(huge(1.0),i=1,4)]
! read arguments from command line
do i=1,command_argument_count()
   call get_command_argument(i,input(2))
   read(input,nml=args,iostat=ios,iomsg=message1)
   ! assume first failure might be because of missing quotes
   if(ios.ne.0)then
      equal_pos=index(input(2),'=')
      if(equal_pos.ne.0)then
         ! requote and try again
         input(2)=input(2)(:equal_pos)//'"'//input(2)(equal_pos+1:len_trim(input(2)))//'"'
         read(input,nml=args,iostat=ios,iomsg=message2)
         if(ios.ne.0)then
            write(*,*)'ERROR UNQUOTED:'//trim(message1)//': when reading '//trim(input(2))
            write(*,*)'ERROR QUOTED  :'//trim(message2)//': when reading '//trim(input(2))
            stop 2
         endif
      else
         write(*,*)'ERROR:'//trim(message1)//': when reading '//trim(input(2))
         stop 4
      endif
   endif
enddo
outfile=trim(outfile)
title=trim(title)
write(*,nml=args,delim='quote')
end program dummymain