8

I'd like to read in some text strings from the command line used to run a program. I'm using the internal subroutine GET_COMMAND_ARGUMENT in a program that basically is something like:

program test
character(len=100) :: argument
call GET_COMMAND_ARGUMENT(1,argument)
print*, argument
end program test

The issue here is that I feel it's a bit dangerous to set the maximum length of the string at compilation time. Some of the arguments are typically files with their path, so they might be very long. A solution involving setting the length statically to 1000 sounds like an ugly workaround.

Isn't there a more elegant way in Fortran to define a string able to contain a chain of characters whose length is only known at run time?

Pythonist
  • 1,937
  • 1
  • 14
  • 25

2 Answers2

10

It is possible to use what are called deferred-length character variables. These are not of a fixed constant length and their use can be found in questions such as a related one about data input.

However, even with a deferred length variable used like (for this is the syntax)

character(len=:), allocatable :: argument
allocate(character(some_length) :: argument)  ! For integer some_length
call GET_COMMAND_ARGUMENT(1,argument)
print*, argument
end

one still has to worry about what some_length should be. If we just choose 100, we're back to where we were.

We have to worry about this because get_command_argument doesn't take such a deferred length argument and allocate it to the desired length. That is

character(len=:), allocatable :: argument
call GET_COMMAND_ARGUMENT(1,argument)  ! Will argument be allocated in the subroutine?
print*, argument
end

offers the answer "no".

Coming, then, to a way to handle this, we look at other (optional) arguments to get_command_argument. In particular, there is one called length:

character(len=:), allocatable :: argument
integer arglen
call GET_COMMAND_ARGUMENT(1,length=arglen)
allocate(character(arglen) :: argument)
call GET_COMMAND_ARGUMENT(1,value=argument)
print*, argument
end

Naturally, one could create a wrapper subroutine which did take an allocatable deferred length character variable and did all that work.

Community
  • 1
  • 1
francescalus
  • 30,576
  • 16
  • 61
  • 96
  • Your approach is much better, somehow I forgot this argument. – Vladimir F Героям слава Feb 02 '16 at 13:34
  • Perhaps also worth noting that this approach works also for `get_environment_variable`. – francescalus Feb 02 '16 at 13:35
  • Thanks, this is of great help and indeed solves the question. I have just a minor question, why is it necessary the rather convoluted `allocate(character(some_length) :: argument)` instead of the more simple `allocate(argument(some_length))` – Pythonist Feb 02 '16 at 14:09
  • 3
    @Onturenio The latter syntax is for arrays. Consider an array of characters `allocate(character(char_len) :: argument(array_len))`. – Vladimir F Героям слава Feb 02 '16 at 14:17
  • 2
    @Onturenio To add to Vladimir F's comment, the `allocate( type-spec :: ...)` form is used to set the type and type parameters on the allocation. The character variable's length is a type parameter. You may see this syntax also for polymorphic variables to set the dynamic type or for parameterized derived types. – francescalus Feb 02 '16 at 14:28
2

I will leave this for completeness, it may be useful to somebody, but francescalus' answer is much better.

Basically, just read it with some default length, check the STATUS variable and if it did not fit, try again.

From the gcc manual:

If the argument retrieval fails, STATUS is a positive number; if VALUE contains a truncated command line argument, STATUS is -1; and otherwise the STATUS is zero.

So:

character(len=:), allocatable :: argument
integer :: stat, n

n = 100
allocate(character(n) :: argument)

do
  call GET_COMMAND_ARGUMENT(1,argument, status=stat)
  if (stat>=0) exit
  deallocate(argument)
  n = n * 2
  allocate(character(n) :: argument)
end do

if (stat>0) error...

argument = trim(argument)

print*, argument