1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FortranAdvent Calendar 2024

Day 23

Fortranでもコマンドライン引数をリストで得たい.

Last updated at Posted at 2025-01-06

背景

コマンドライン引数を得る方法として,Fortranではcommand_argument_count()関数で引数の数を取得した上でget_command_argument()サブルーチンで特定のコマンドライン引数値を得ることができます.この方法はシンプルでよいですが,コマンドライン引数一覧をオブジェクトとして保持することはできず,特定のコマンドライン引数を得るためには毎回これらの手続を呼ぶ必要があります.

一方で,Pythonでは,sys.argv()関数によりコマンドライン引数一覧をリストとして得られます.そのため,コマンドライン引数一覧を頻繁に参照する場合にはより便利です.

そこで,Fortranでもコマンドライン引数をリストとして得るべく,実装しました.

リストクラスの作成

複数のデータ要素をもつ1次元配列はリストと呼ばれます.リストの各要素には1つのインデックスが対応しており,インデックスを参照することで要素を受け取ることができます.
Fortranでリストを実装する方法は幾通りもありえますが,ここでは要素を文字型配列として持つ方法を採用します.

まず,リストの要素をlist_content_型の派生型として定義します.

type :: List_content_
    character(:),allocatable :: char
end type

次に,リスト要素の配列をメンバーにもつリストList_型配列を作成します.
併せて,初期化用サブルーチンとゲッターを定義しておきます.

    type :: List_
        type(List_content_), allocatable :: content(:)
    contains
        procedure, public :: new => new_list_listclass
        procedure, public :: get => get_list_content_listclass
    end type

また,コマンドライン引数取得用の関数も定義しておきます.

    interface argv
        module procedure argv_get_cmd_args_as_list
    end interface

以下が完全な実装です.

module ListClass
    use iso_fortran_env
    implicit none

    type :: List_content_
        character(:),allocatable :: char
    end type

    type :: List_
        type(List_content_), allocatable :: content(:)
    contains
        procedure, public :: new => new_list_listclass
        procedure, public :: get => get_list_content_listclass
    end type


    interface argv
        module procedure argv_get_cmd_args_as_list
    end interface
contains

! #####################################################
pure subroutine new_list_listclass(this, length)
class(List_), intent(inout) :: this
integer(int32), intent(in) :: length
integer(int32) :: i

if (allocated(this%content)) then
   deallocate (this%content)
end if

allocate (this%content(length))
do i = 1, length
   this%content(i)%char = ""
end do

end subroutine
! #####################################################

! #####################################################
pure function get_list_content_listclass(this, idx) result(ret)
    class(List_), intent(in) :: this
    integeR(int32), intent(in) :: idx
    character(:), allocatable :: ret

    if (allocated(this%content))then
       ret = this%content(idx)%char
    elseif (.not. allocated(this%content)) then
       ret = ""
       return
    end if

end function
! #####################################################


! #####################################################
function argv_get_cmd_args_as_list() result(ret)
    type(List_) :: ret
    integer(int32) ::i, n, length, status
    character(:),allocatable :: this_arg
    !character(:),allocatable ::line

    n = command_argument_count()
    call ret%new(n) 
    
    do i = 1,n
       call  get_command_argument(i,length=length,status=status)
       if(allocated(this_arg))then
          deallocate(this_arg)
       endif      
       allocate(character(length)::this_arg)
       
       call  get_command_argument(i,this_arg,status=status)
       
       ret%content(i)%char = this_arg
    enddo
    

 end function
! #####################################################

end module ListClass

用例

第一引数,第二引数を取得してみます.

program main
    use ListClass
    implicit none
    
    type(List_) :: args

    args = argv()
    print *, args%get(1)
    print *, args%get(2)
    
end program main

./a.out hello world
と実行すると,無事,以下の出力を得ます.

$ ./a.out hello world
 hello
 world

また,コマンドライン引数を単発で取得する場合にも,ゲッターをユーザー定義演算子化すると,やや明快になります.

    interface operator(.get.)
        module procedure get_element_of_listclass
    end interface
 ! #####################################################
function get_element_of_listclass(this_list,idx) result(ret)
    character(:),allocatable :: ret
    type(List_),intent(in) :: this_list
    integer(int32),intent(in) :: idx
 
    ret = ""
    if(allocated(this_list%content) )then
       if (size(this_list%content) >= idx )then
          ret = this_list%content(idx)%char
       endif
    endif
 
 end function
 ! #####################################################

以下のように実行できます.

program main
    use ListClass
    implicit none
    
    print *, argv() .get. 1
    print *, argv() .get. 2
    
end program main

出力は同じく以下の通りとなります.

$ ./a.out hello world
 hello
 world

まとめと拡張

簡単な派生型を作ることで,コマンドライン引数のリストのようなものを得られるようにしました.
ここで,

   public :: assignment(=)
   interface assignment(=)
      module procedure assign_real64, assign_int32, ...
   end interface

のようにして,文字型配列を浮動小数点数型,整数型等に変換できるようにしておくと,よりリストに近いものになります.
また,簡単な拡張でsplit等,文字列とリストを相互に変換することもでき,pythonライクな使用感が得られます.

Appendix

1ファイルにまとめたコードはこちらになります.

module ListClass
    use iso_fortran_env
    implicit none
    
    !type :: List_content_
    !    character(:),allocatable :: char
    !end type

    type :: List_content_
        character(:),allocatable :: char
    end type

    type :: List_
        type(List_content_), allocatable :: content(:)
    contains
        procedure, public :: new => new_list_listclass
        procedure, public :: get => get_list_content_listclass
    end type


    interface argv
        module procedure argv_get_cmd_args_as_list
    end interface


    interface operator(.get.)
        module procedure get_element_of_listclass
    end interface
contains

! #####################################################
pure subroutine new_list_listclass(this, length)
    class(List_), intent(inout) :: this
    integer(int32), intent(in) :: length
    integer(int32) :: i

    if (allocated(this%content)) then
       deallocate (this%content)
    end if
    
    allocate (this%content(length))
    do i = 1, length
       this%content(i)%char = ""
    end do

end subroutine
! #####################################################

! #####################################################
pure function get_list_content_listclass(this, idx) result(ret)
    class(List_), intent(in) :: this
    integeR(int32), intent(in) :: idx
    character(:), allocatable :: ret

    if (allocated(this%content))then
       ret = this%content(idx)%char
    elseif (.not. allocated(this%content)) then
       ret = ""
       return
    end if

end function
! #####################################################


! #####################################################
function argv_get_cmd_args_as_list() result(ret)
    type(List_) :: ret
    integer(int32) ::i, n, length, status
    character(:),allocatable :: this_arg
    !character(:),allocatable ::line

    n = command_argument_count()
    call ret%new(n) 
    
    do i = 1,n
       call  get_command_argument(i,length=length,status=status)
       if(allocated(this_arg))then
          deallocate(this_arg)
       endif      
       allocate(character(length)::this_arg)
       
       call  get_command_argument(i,this_arg,status=status)
       
       ret%content(i)%char = this_arg
    enddo
    

 end function
! #####################################################

 ! #####################################################
function get_element_of_listclass(this_list,idx) result(ret)
    character(:),allocatable :: ret
    type(List_),intent(in) :: this_list
    integer(int32),intent(in) :: idx
 
    ret = ""
    if(allocated(this_list%content) )then
       if (size(this_list%content) >= idx )then
          ret = this_list%content(idx)%char
       endif
    endif
 
 end function
 ! #####################################################
 

end module ListClass

program main
    use ListClass
    implicit none
    
    type(List_) :: args


    args = argv()
    print *, args%get(1)
    print *, args%get(2)

    print *, argv() .get. 1
    print *, argv() .get. 2
    
end program main
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?