コマンド ライン引数を用いる利点と Fortran における使用例
本稿にはコマンド ライン引数を用いることの利点、 ならびに Fortran におけるコマンド ライン引数使用の例を示しています。
私が考えるコマンド ライン引数を利用する利点は以下の通りです。
- 使いまわしやすいプログラムを書ける
- バッチ処理が可能になる
入力ファイル、出力ファイル、パラメータなどを替えて同じプログラムを走らせたいとき、これらの値をハード コーディングしていると、毎回違う値を入力してコンパイルし直す必要があります。しかし、これらをコマンド ライン引数として与えるプログラムにしていれば、コンパイルし直すことなく使用することができます。
バッチ ファイルにまとめて書くことで、複数の処理をまとめて実行することもできます。例えば、Fortran で計算し、Python で可視化する、という処理を一括で行えます。 何を入力したかをバッチ ファイルに残すことができるので、計算を再現するための条件を残しておくこともできます。また、バッチ ファイル自体がプログラムの使用例になります。
Fortran に限らず、コマンド ライン引数を用いたプログラムにおける処理の基本的な流れは以下の通りです。
コマンド ライン引数を受け取ったら、構文の解析を行う。
- 引数の個数、値の範囲、型などが正しいか分析します。
構文にエラーがあれば、ユーザーに使用方法を通知する。
- 例外を投げるだけだと、ユーザーには意味不明の文が流れるので、 きちんと使用方法を提示します。
意味がわかる名前の変数に、受け取った引数の値をすぐに代入する。
- args(1)、args(2)... のような名前のまま使うと、 何を格納した変数かわからないためです。
- スコープ最小の原則にそむくように見えますが、 スコープ最小の原則の目的は覚えておくべきことを最小にすることです。 すぐに意味のある変数に代入することで、args(1)、args(2)... が何であるかを 覚えておく必要がなくなります。
コマンド ライン引数を Fortran で用いるには get_command_argument*1 を用います。 get_command_argument はそのままでは使いにくいので、command_getter クラスを作りました。
この command_getter を用いたプログラムの例を示します。
メイン プログラムは以下の通りです*2。
! コマンド ライン引数取得のテストです。 ! 名前、年齢、体重をコマンド ライン引数で与えます。 ! 標準出力にコマンド ライン引数で与えた名前、年齢、体重を表示します。 program main_program use, intrinsic :: iso_fortran_env use class_command_getter implicit none ! 名前 character(:), allocatable :: name ! 年齢 integer(int32) age ! 体重 real(real64) weight ! コマンド ライン引数を取得するクラス type(command_getter) args_getter ! コマンド ライン引数を解析します。 if(args_getter%get_size() /= 3) then ! エラーを通知してプログラムを終了します。 write(error_unit, *) "There must be following command line arguments." write(error_unit, *) " - name: character" write(error_unit, *) " - age: integer" write(error_unit, *) " - weight (kg): real number or integer" write(error_unit, *) "(e.g.) command_getter_test.exe Rikishi 25 150.6" write(error_unit, *) "Program ended." stop end if ! 名前を決定します。 name = args_getter%get(1) ! 年齢を決定します。 age = args_getter%to_int32(2) ! 体重を決定します。 weight = args_getter%to_real64(3) ! 名前、年齢、体重を標準出力に表示します。 print'(A, A)', "Name: ", name print'(A, I3)', "Age: ", age print'(A, F5.1, A)', "Weight: ", weight, " kg" end program
class_command_getter は以下の通りです。
! コマンド ライン引数を取得するクラスです。 module class_command_getter use, intrinsic :: iso_fortran_env implicit none private ! コマンド ライン引数を表す構造体です。 type, private :: command_line_argument character(:), private, allocatable :: value end type ! コマンド ライン引数を取得するクラスです。 type, public :: command_getter ! コマンド ライン引数を取得していれば true、そうでなければ false logical, private :: has_values = .false. ! コマンド ライン引数 type(command_line_argument), private, allocatable :: value(:) contains procedure, private, pass :: get_all_command_line_argument procedure, public, pass :: get procedure, public, pass :: get_size procedure, public, pass :: to_int32 procedure, public, pass :: to_real64 end type contains ! i 番目のコマンド ライン引数を返します。戻り値の型は character です。 ! ! @param i コマンド ライン引数の順番 (1 始まり、int32) ! @return i 番目のコマンド ライン引数 function get(this, i) result(arg) ! 引数 class(command_getter), intent(inout) :: this integer(int32), intent(in) :: i ! 戻り値 character(:), allocatable :: arg ! コマンド ライン引数を取得していなければ、コマンド ライン引数を全て取得します。 if(.not. this%has_values) then call this%get_all_command_line_argument() end if arg = this%value(i)%value end function ! コマンド ライン引数の個数を返します。 ! ! @return コマンド ライン引数の個数 function get_size(this) result(number_of_args) ! 引数 class(command_getter), intent(inout) :: this ! 戻り値 integer(int32) number_of_args ! コマンド ライン引数を取得していなければ、コマンド ライン引数を全て取得します。 if(.not. this%has_values) then call this%get_all_command_line_argument() end if number_of_args = size(this%value) end function ! 指定されているコマンド ライン引数を全て取得します。 subroutine get_all_command_line_argument(this) ! 引数 class(command_getter), intent(inout) :: this ! コマンド ライン引数 character(:), allocatable :: arg ! コマンド ライン引数の個数 integer(int32) number_of_args ! コマンド ライン引数の文字列長を格納する配列 integer(int32) length ! コマンド ライン引数取得時の状態 integer(int32) status ! ループ カウンタ integer(int32) i ! コマンド ライン引数の個数を取得します。 number_of_args = command_argument_count() ! コマンド ライン引数を格納する配列を生成します。 allocate(this%value(number_of_args)) do i = 1, number_of_args ! i 番目のコマンド ライン引数の長さを取得します。 call get_command_argument(i, length=length, status=status) ! コマンド ライン引数の取得に失敗すれば、プログラムを終了します。 if (status /= 0) then write(error_unit, *) "Command line argument retrieval failed." write(error_unit, *) "Program ended." stop end if ! i 番目のコマンド ライン引数を取得します。 allocate(character(length) :: arg) call get_command_argument(i, arg, status=status) ! コマンド ライン引数の取得に失敗すれば、プログラムを終了します。 if (status /= 0) then write(error_unit, *) "Command line argument retrieval failed." write(error_unit, *) "Program ended." stop end if ! コマンド ライン引数を格納します。 this%value(i)%value = arg deallocate(arg) end do this%has_values = .true. end subroutine ! i 番目のコマンド ライン引数を int32 に変換します。 ! ! @param i int32 に変換したいコマンド ライン引数のインデックス ! @return int32 の整数 function to_int32(this, i) result(output_integer) ! 引数 class(command_getter), intent(inout) :: this integer(int32), intent(in) :: i ! 戻り値 integer(int32) output_integer ! iostat 指定子 integer(int32) iostat ! コマンド ライン引数を取得していなければ、コマンド ライン引数を全て取得します。 if(.not. this%has_values) then call this%get_all_command_line_argument() end if ! コマンド ライン引数を int32 に変換します。 read(this%value(i)%value, *, iostat=iostat) output_integer ! 変換にエラーがあればプログラムを終了します。 if(iostat > 0) then write(error_unit, *) "An error occurred in to_int32 in class_command_getter." write(error_unit, *) "Program ended." stop end if end function ! i 番目のコマンド ライン引数を real64 に変換します。 ! ! @param i real64 に変換したいコマンド ライン引数のインデックス ! @return real64 の実数 function to_real64(this, i) result(output_real_value) ! 引数 class(command_getter), intent(inout) :: this integer(int32), intent(in) :: i ! 戻り値 real(real64) output_real_value ! iostat 指定子 integer(int32) iostat ! コマンド ライン引数を取得していなければ、コマンド ライン引数を全て取得します。 if(.not. this%has_values) then call this%get_all_command_line_argument() end if ! コマンド ライン引数を real64 に変換します。 read(this%value(i)%value, *, iostat=iostat) output_real_value ! 変換にエラーがあればプログラムを終了します。 if(iostat > 0) then write(error_unit, *) "An error occurred in to_real64 in class_command_getter." write(error_unit, *) "Program ended." stop end if end function end module
*1:https://gcc.gnu.org/onlinedocs/gfortran/GET_005fCOMMAND_005fARGUMENT.html
*2:型チェックの良い方法が思いつかなかったので、今回はコマンド ライン引数の数だけを解析しています。