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:
krackenbasic -r 333.333 -f /home/urbanjs/testin -l -i 300
with very little code:
program krackenbasic use M_kracken character(255) filename logical lval ! define command arguments, default values ! and crack command line call kracken('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 krackenbasic
SUBROUTINE kracken(verb, string) ! arbitrary command name, usually 'cmd' CHARACTER(len=*), intent(in) :: verb ! prototype command to define keywords and defaults ! this string is simply a list of all keywords and their ! default values exactly as you would type them on the ! command line CHARACTER(len=*), intent(in) :: string END SUBROUTINE kracken CALL KRACKEN('cmd','-start 1 -end 100')
SUBROUTINE retrev(name, val, len, ier) ! parameter name of form VERB_KEYWORD CHARACTER(len=*),intent(in) :: name ! returned parameter value CHARACTER(len=*),intent(out):: val ! length of returned string INTEGER,intent(out) :: len ! error flag. Any non-zero value means an error occurred INTEGER,intent(out) :: ier END SUBROUTINE retrev CALL RETREV('cmd_start',val,len,ier)
SUBROUTINE string_to_real(chars, valu, ierr) ! string representing numeric value to convert CHARACTER(len=*), intent(in) :: chars ! real value returned from reading string CHARS REAL, intent(out) :: valu ! if non-zero an error occurred INTEGER, intent(out) :: ierr END SUBROUTINE string_to_real val='1' call string_to_real(VAL,x,ierr)
SUBROUTINE delim(INLINE,array,n,icount,ibegin,iterm,ilen,dlim) CHARACTER(*) INLINE, dlim *(*) ! INLINE is the string to break into tokens CHARACTER array(n)*(*) ! array is the array to fill with tokens INTEGER icount, ibegin(n), iterm(n), ilen ! icount is how many tokens are found ! ibegin(1:icount) = starting column numbers for the tokens in INLINE ! iend(1:icount)=ending column numbers for the tokens in INLINE ! ilen is the position of last non-blank character in INLINE ! dlim is a string of single characters to use as delimiters END SUBROUTINE delim
If ARRAY(N) fills before reaching the end of the line the routine stops. Check "if(iend(icount) .eq. ilen)" to see if you got to the end.
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
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)
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, -par3, and -- . And here is a main program that does just that ...
program krackentest use M_kracken ! call with an arbitrary verb name and a prototype string that defines ! allowable switch names and default values call kracken('mycommand', &' -i 10 -r 200.3 -l -par1 "#N#" -par2 -par3 10 20 30 -- ') ! 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 -- 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_kracken 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 Convention for last parameter because multi-word unquoted values are allowed call listem('mycommand_-','--'.false.) end C=======================================================================-------- SUBROUTINE listem(keyword,label,toreal) USE M_kracken 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 kracken('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
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 kracken library download the following source files:
# compile a demo program and the source for kracken(3F) and # associated routines g95 M_kracken.f90 krackentest.f90 # or if you prefer ANSI-77 syntax an alternate (static) version is available g77 krackentest.f kracken.f
Here are some donated contributions that will be incorporated directly into M_KRACKEN(3f) in the future ...
My purpose here is to make something very simple and portable that others can use reliably. I have made and used variants of kracken for years; for me it was just calls to mostly-f77 libraries used for a variety of other purposes. While extracting parts of the library to make a simple stand-alone version I realized anyone using this would be better served by using clean f90+ syntax. Lately being "clean" means I can compile it with an "F" compiler (if it doesn't use stuff "past" F). So I won't be maintaining the public f77 version, as I have a bigger better one I will be maintaining. And the whole idea of libraries is to not have to maintain duplicate code.
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).
If you don't need GNU/Unix syntax and you have a Fortran 2003-compliant compiler, an alternative in testing that uses NAMELIST is promising and very simple. I also have a few links to related sites in the optional "OMNIUM-GATHERIUM" section.