Enter Home Page

PARSE(3F): The Ultimate Fortran Command Line Argument Cracker

John S. Urban (last change: Dec. 2013)

ABSTRACT

In Fortran 2003, GET_COMMAND(3f) provides a standard method to read command-line arguments. But this little library goes much further and lets you use Unix-like syntax very easily. You can call your command like this:

 PARSEbasic -r 333.333 -f /home/urbanjs/testin -l -i 300

with very little code:

Example Usage

program PARSEbasic
   use M_PARSE
   character(255) filename
   logical lval
!  define command arguments, default values and crack command line
   call PARSE('cmd','-i 10 -r 10e3 -d 4.1123344d0 -l .false. -f input')
!  get values
   call retrev('cmd_f',filename,iflen,ier) ! get -f FILENAME
   lval = lget('cmd_l')                    ! get -l present?
   rval = rget('cmd_r')                    ! get -r RVAL
   dval = dget('cmd_d')                    ! get -d DBLEVAL
   ival = iget('cmd_i')                    ! get -i IVAL
!  all done parsing; do something with the values
   print *, "filename=",filename(:iflen)
   print *, " i=",ival, " r=",rval, " l=",lval
end program PARSEbasic

PARSE(3f) provides:

The Routines

There are also convenience functions for getting simple values

      lval=lget(VERB_ARGNAME) !gets a "logical" value. 
      rval=rget(VERB_ARGNAME) !gets a "real" value.
      dval=rget(VERB_ARGNAME) !gets a "doubleprecision" value.
      ival=iget(VERB_ARGNAME) !gets a "integer" value
      sval=sget(VERB_ARGNAME) !gets a "character" value

Optional Routines

If you want to allow command-like input to your program you can parse your own strings using DISSECT(3f).

SUBROUTINE dissect(verb,init,pars,ipars)
!
!@(#) convenient call to parse() -- define defaults, then process user input
!
      !   verb   is the name of the command to be reset/defined  and then set
      CHARACTER(LEN=*),INTENT(IN)  ::  verb

      !   init   is a string used to add a new command or to reset an old one.
      !          This string is usually hard-set in the program.
      CHARACTER(LEN=*),INTENT(IN)  ::  init

      !   pars   is a string defining the command options to be set, usually
      !          from a user input file
      CHARACTER(LEN=*),INTENT(IN)  ::  pars

      !   ipars  is the length of the user-input string pars.
      INTEGER,INTENT(IN)           ::  ipars

   CALL DISSECT('plotsize',' -x 8.5 -y 11',my_input_string,ipars)

Extended Example

The following example is a bit long compared to typical use; but exercises all the options. The command generated will have parameters -i, -r, -l, -par1, -par2, and -par3. And here is a main program that does just that ...

      program PARSEtest
      use M_PARSE

! call with an arbitrary verb name and a prototype string that defines
! allowable switch names and default values

      call PARSE('mycommand',
     &' -i 10 -r 200.3 -l -par1 "#N#" -par2 -par3 10 20 30 -files')

! that's it. You defined your command arguments and their default
! values and parsed the user-supplied command line arguments.
!
! Now you can just retrieve the values as strings using
! names of the form VERB_SWITCHNAME anywhere in your program.
! Note that the special name "VERB_oo"  is for the string
! before any switch. to see how look at the SAMPLES() procedure

      call samples()
      end 

When the user types

 mycommand  one two -l -i 10 -three four -files file1 file2 file3

the program should complain about -three not being a valid option and will create names that can be used with RETREV(3f).

So lets take a look at something that calls RETREV(3F) and the convenience functions (LGET(),IGET(),RGET(),DGET(),....). For completeness, I show the use of string_to_real(3F) to convert the string returned by RETREV(3F) to a number; and the use of DELIM(3F) to break down a returned list into words. Note that as long as the routines are not called many times (because they are slower than common alternatives), you can just put the calls to RETREV() where you need the information instead of passing the values around or putting them into a COMMON or MODULE.

C=======================================================================--------
      SUBROUTINE samples
      USE M_PARSE

      CHARACTER*255 value
      LOGICAL lget,lval

C everything before any switch is always VERB_oo
      call RETREV('mycommand_oo',value,len,ier)
      write(*,*)'before any switch=',value(:len)

C Getting an integer from -i value
      inumber=IGET('mycommand_i')
      write(*,*)'value for -i =',inumber,' divided by 2 is ',inumber/2

C Getting a real number from -r value 
      anumber=RGET('mycommand_r')
      write(*,*)'value for -r =',anumber,'divided by 2 is ',anumber/2

C Getting a logical value from -l value 
      lval=LGET('mycommand_l')
      write(*,*)'value for -l =',lval

C Getting a string (with a default) from -par1
      call RETREV('mycommand_par1',value,len,ier)
      write(*,*)'value for -par1 =',value(:len)

C Getting a string (with no default) from -par2
      call RETREV('mycommand_par2',value,len,ier)
      write(*,*)'value for -par2 =',value(:len)

C Getting a string with a multi-word default and splitting it from -par3
      call listem('mycommand_par3','-par3',.true.)

C List of files
      call listem('mycommand_files','-files'.false.)

      end
C=======================================================================--------
      SUBROUTINE listem(keyword,label,toreal)
      USE M_PARSE
C     Just getting fancy and showing the use of DELIM(3F)
C     SAMPLE that decomposes list of strings and optionally, numbers
C     and prints it.
C     Delimit with space, comma, or colon
      LOGICAL toreal
      CHARACTER*(*) keyword
      CHARACTER*(*) label
      CHARACTER*255 value

C     for DELIM call if you want to break down a list
      CHARACTER*255 array(132)
      INTEGER ibegin(132)
      INTEGER iterm(132)

C     get the value of the keyword and the length
      CALL RETREV(keyword,value,len,ier)
      WRITE(*,*)'value for ',label,'=',value(:len)
C     split the list into one word per array
      call DELIM(value,array,132,igot,ibegin,iterm,ilen,' ,:')
C     print and optionally convert each word into a numeric value
      DO i10=1,igot
         WRITE(*,*)i10,') ',array(i10)(:len_trim(array(i10)))
         IF(toreal)THEN
            CALL string_to_real(array(i10),anumber,ier)
            IF(ier.eq.0)THEN
               WRITE(*,*)' which is a number'
            ELSE
               WRITE(*,*) ' which is not a number'
            ENDIF
         ENDIF
      ENDDO
      RETURN
      END
C=======================================================================--------

This shows the expected output of the execution:

 # Remember the command was defined by
 # call PARSE('mycommand',&
 # &' -i 10 -r 200.3 -l -par1 "#N#" -par2 -par3 10 20 30 -- ')
 #
 # user types
 mycommand  one two -l -i 10 -three four -- file1 file2 file3
 # response should be
 >:E-R-R-O-R: UNKNOWN OPTION mycommand_three
 before any switch=one two
 value for -i =10
 divided by 2 is  5
 value for -r =200.3
 divided by 2 is  100.15
 value for -par1 =#N#
 value for -par2 =
 value for -par3=10 20 30
 1) 10
  which is a number
 2) 20
  which is a number
 3) 30
  which is a number
 value for --=file1 file2 file3
 1) file1
 2) file2
 3) file3

Special processing

A little bit of special processing occurs

Interactive menu mode (f90+ version only)

Th menu mode feature is in a state of flux and may change significantly ...

All commands automatically have the parameter "-?". If it is present, a menu appears after any specified options have been applied that allows for changing parameters interactively.

The default prompts are the keywords themselves and their current values. To set your own prompts call SETPROMPTS(3f):

    call setprompts(verb_name,options_and_prompts)

where the special prompt string "#N#" means to not allow prompting for this parameter. For example:

     ! set prompts for interactive mode ...
     call setprompts('copy','                           &
     & -oo "#N#"                                        &
     & -i Enter input file name                         &
     & -o Enter output file name                        &
     & -version "#N#"                                   &
     & -help "#N#"                                      &
     & ')
     call PARSE('copy','-i -o -version .false. -help .false')

Then the command

       copy -?
    
would only prompt for the -i and -i parameters.

Download files

If you read this far, you probably want the source. You'll be happy to know it's annotated more thoroughly than this example is.

To get a demonstration program and the source for the PARSE library download the following source files:

  # compile a demo program and the source for PARSE(3F) and
  # associated routines
  g95 M_PARSE.f90 PARSEtest.f90
  # or if you prefer ANSI-77 syntax an alternate (static) version is available
  g77   PARSE-test-1.f   PARSE.f

f90+ files(preferred)

f77+ files (considered frozen)

The f77 version found here will not be changing frequently

The primary idea of libraries is to eliminate duplicate code. Therefore, while the f90+ version is actively maintained, the f77 version should be considered essentially static. That being said, there are cases where the f77 version is preferred. So the f77 has been upgraded to remove the external function JULEN(3f) and replace it with the standard f90 intrinsic function LEN_TRIM(3f) and to make explicit declarations for all variables (20131206). Thanks go to Walid Keyrouz!

If you are having problems

The routine GET_COMMAND_ARGUMENTS() is the only one that actually reads from the command line. The f90+ version assumes your compiler has GET_COMMAND_ARGUMENTS(). The f77+ version assumes your compiler has GETARGS() and IARGC(). If neither is the case and you don't know how to make your own GET_COMMAND_ARGUMENTS() look-alike, It is VERY likely you can get a GET_COMMAND_ARGUMENT() from the F2KCLI package from WINTERACTER.

You may note that the parsing rules are not identical to Unix, although very similar. There is no way to terminate a keyword except by starting a new keyword; and the keyword -oo is implied after the verb; and you cannot combine keywords (-ir is not equivalent to -i -r, which is sometimes allowed on Unix commands). You may find the way to include a literal double-quote character (") is the most unlike Unix (see code comments for details).

SEE ALSO:

If you don't need GNU/Unix syntax and you have a Fortran 2003-compliant compiler, an alternative that uses NAMELIST is promising and very simple. I also have a few links to related sites in the optional "OMNIUM-GATHERIUM" section.