FORTRAN(Fortran)入門

すごいとは思わないけれど、何か気になるFORTRAN。FORTRAN学習の先には何かある予感がします。学習の先に何もないかもしれないし、すごい悟りを得るかもしれない。学校でFORTRANの実習したときもそうだったのですが、FORTRANで何ができるんだろう、と不思議に感じます。まあ、いろんなことができるんでしょうけど。若い時に真面目に勉強しておけば、今FORTRANやり直しすることもなかったかもしれませんが、その時にFORTRANで感動が起こらなかったのでしょうがないです。
当時言われていたFORTRANやCといった高級言語よりも、私はアセンブラが面白くて仕方がなかったので、高級言語をまったく学ぶ気がありませんでした。
今はコマンドにハマってますので、どちらかといったら高級なのでしょうけど。

FORTRAN77覚えようと思います。(90や95も)
世界初の高級言語なので、いつか使えるようになればいいと思っていましたが、今回から始めることにしました。 ちなみにFORTRANは、FORmula TRANslationが由来だそうです。

コンパイラはgfortranを使うことにします。95系77系両方使えます。

LinuxでFortranことはじめ

インストール

インストールは簡単。apt-get使うだけです。

# apt-get install gfortran

Hello World

新しい言語を学ぶ時のおきまり、Hello Worldを作ってみます。

takk@deb9:~$ cat hello.f95
program hello
  print *, 'Hello World'
end program hello
takk@deb9:~$

プログラムの開始と終了の位置を指定する必要があります。

ビルド(コンパイル)

ビルドは、gfortranを実行します。デフォルトの実行ファイルはa.outです。
ソースファイルの拡張子をf95にして、ビルドする時に何もつけないとFORTRAN95でのビルドになります。

takk@deb9:~$ gfortran hello.f95
takk@deb9:~$ ./a.out
 Hello World
takk@deb9:~$

FORTRAN77でビルドするには、拡張子を.fにして、-std=legacyを指定します。おそらく。
(学習過程で間違いを見つけたらこの記事も修正します)

takk@deb9:~$ cat hello.f
      PROGRAM hello
      print *, 'Hello World'
      STOP
      END
takk@deb9:~$ gfortran -std=legacy hello.f
takk@deb9:~$ ./a.out
 Hello World
takk@deb9:~$

Fortran 書式付き表示

FORMAT 1

コマンドラインを使いこなす上で、プログラミング知識も必要かと思い、いろんな言語を少しずつ学習していますが、FORTRANを理解するのが一番苦労してるかもしれません。

世界初の高級言語でスパコンにも使われている。66,77,90,95といった制定された年別に言語仕様が存在する。ぐらいは分かってきました。

前回は、さくっとprint命令を使って、Hello Worldを書いてみたものの、果たしてFORTRAN77にPRINT命令があったかどうかわかりません。というか、手元のFORTRAN77の教科書に書いてないです。リファレンスには、いったいどこを見ればよいのでしょう。
まあ、それもおいおい調べていきます。
今回は、手元の古い教科書を頼りに、print文ではなく、WRITEとFORMATを使って表示をしていきたいと思います。

サンプルプログラムです。

takk@deb9:~$ cat disp.f
       A=500.5
       WRITE (*,123) A
123    FORMAT ('A=',F6.2)
       STOP
       END
takk@deb9:~$

A=500.5は、変数Aに500.5という数値を代入しています。
WRITEと次の行のFORMATはセットで使うのですが、WRITE関数の第2引数の123というのが、どの書式を使うのかの指定になります。適当に123というラベル名をつけてますが、存在しないラベルを指定してはいけません。以下のようにエラーになります。

takk@deb9:~$ gfortran disp.f
disp.f:2:72:

        WRITE (*,124) A
                                                                        1
Error: FORMAT label 124 at (1) not defined
takk@deb9:~$

ラベルなのだから文字列でもいいのかと思いきや、思いっきりエラーになります。

takk@deb9:~$ gfortran disp.f
disp.f:3:1:

 DEF123 FORMAT ('A=',F6.2)
 1
Error: Non-numeric character in statement label at (1)
disp.f:3:1:

 DEF123 FORMAT ('A=',F6.2)
 1
Error: Unclassifiable statement at (1)
disp.f:2:16:

        WRITE (*,DEF123) A
                1
Error: FORMAT tag at (1) must be of type default-kind CHARACTER or of INTEGER
takk@deb9:~$

そもそも6カラム目までは行番号を各エリアなので、整数が基本です。

桁数が多くてもエラーとなります。

takk@deb9:~$ gfortran disp.f
disp.f:2:22:

        WRITE (*,100000) A
                      1
Error: Too many digits in statement label at (1)
takk@deb9:~$

5桁までのようです。

takk@deb9:~$ cat disp.f
       A=500.5
       WRITE (*,99999) A
99999  FORMAT ('A=',F6.2)
       STOP
       END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
A=500.50
takk@deb9:~$

ラベルの数字は上の桁を0としても良いようです。

takk@deb9:~$ cat disp.f
       A=500.5
       WRITE (*,00009) A
00009  FORMAT ('A=',F6.2)
       STOP
       END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
A=500.50
takk@deb9:~$

FORMAT 2

FORMATのラベルの順番は数値の大小関係ないようです。

takk@deb9:~$ cat disp.f
       A=500.5
       WRITE (*,11111) A
       WRITE (*,99999)
99999  FORMAT ('HELLO')
11111  FORMAT ('A=',F6.2)
       STOP
       END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
A=500.50
HELLO
takk@deb9:~$

FORMATを定義する位置はどこでもよさそう。なかなか柔軟ですね。

takk@deb9:~$ cat disp.f
       A=500.5
99999  FORMAT ('A=',F6.2)
       WRITE (*,11111)
11111  FORMAT ('HELLO')
       WRITE (*,99999) A
       STOP
       END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
HELLO
A=500.50
takk@deb9:~$

変数を指定しているのに、FORMATで書式を指定していないと実行時にエラーになります。

takk@deb9:~$ cat disp.f
       A=500.5
       WRITE (*,99999) A
99999  FORMAT ('HELLO')
       STOP
       END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
At line 2 of file disp.f (unit = 6, file = 'stdout')
Fortran runtime error: Insufficient data descriptors in format after reversion

Error termination. Backtrace:
#0  0x7f579e1f7d4a
#1  0x7f579e1f8825
#2  0x7f579e1f8f79
#3  0x7f579e2be0db
#4  0x55f49cb44985
#5  0x55f49cb449d9
#6  0x7f579d7032b0
#7  0x55f49cb447f9
#8  0xffffffffffffffff
takk@deb9:~$

FORMATの引数は、複数指定可能です。

takk@deb9:~$ cat disp.f
       WRITE (*,11111)
11111  FORMAT ('AAA','BBB','CCC')
       STOP
       END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
AAABBBCCC
takk@deb9:~$

空白文字は数を指定できます。AAAとBBBの間に10個の空白を入れたい場合は、10Xと指定します。

takk@deb9:~$ cat disp.f
       WRITE(*,11111)
11111  FORMAT('AAA',10X,'BBB','CCC')
       STOP
       END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
AAA          BBBCCC
takk@deb9:~$

改行の仕方は? というと、まだ覚えてません。

FORMAT記述子I

前回使ったように、クォートで囲った文字列をカンマをつけて引数に指定すると、文字列が連続で表示されますね。

takk@deb9:~$ cat disp.f
       WRITE(*,11111)
11111  FORMAT('AAA','BBB','CCC')
       STOP
       END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
AAABBBCCC
takk@deb9:~$

改行にするには、カンマをスラッシュにすれば良いようです。
これは到底思いつかないです。古い教科書が頼りでした。

takk@deb9:~$ cat disp.f
       WRITE(*,11111)
11111  FORMAT('AAA'/'BBB'/'CCC')
       STOP
       END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
AAA
BBB
CCC
takk@deb9:~$

次は数値の書式です。FORTRANのFORMATの数値の記述子には以下があります。

整数の書式 I
実数の書式 F E G

使っていきます。整数型の記述子Iから。このような簡単なプログラムで確認します。

takk@deb9:~$ cat disp.f
       A=200
       WRITE(*,11111) A
11111  FORMAT(I5)
       STOP
       END
takk@deb9:~$

ビルドして実行してみましょう。

takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
At line 2 of file disp.f (unit = 6, file = 'stdout')
Fortran runtime error: Expected INTEGER for item 1 in formatted transfer, got REAL
(I5)
 ^

Error termination. Backtrace:
#0  0x7f2113e68d4a
#1  0x7f2113e69825
#2  0x7f2113e69f79
#3  0x7f2113f21378
#4  0x7f2113f2c636
#5  0x7f2113f2e812
#6  0x55ccfcb52985
#7  0x55ccfcb529d9
#8  0x7f21133742b0
#9  0x55ccfcb527f9
#10  0xffffffffffffffff
takk@deb9:~$

なぜエラー・・・?

エラーメッセージにREALとあるので実数型になってるのでしょうか。

無理やり整数型にしてみましょうか。感です。

WRITE文で指定している変数AにINT関数を適応してみました。

takk@deb9:~$ cat disp.f
       A=200
       WRITE(*,11111) INT(A)
11111  FORMAT(I5)
       STOP
       END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
  200
takk@deb9:~$

ビルドが通ってびっくり。結果も合ってます。

INT関数で整数型になったということは、A=200は実数なんでしょうね。
整数の宣言が必要なのでしょうか。

整数の変数宣言はINTEGERを使うようです。
宣言してから使ってみます。

takk@deb9:~$ cat disp.f
       INTEGER A
       A=200
       WRITE(*,11111) A
11111  FORMAT(I5)
       STOP
       END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
  200
takk@deb9:~$

今度はエラー出ませんでした。
最近のスクリプトのように、変数の型宣言なしで使うのは危ないってことでしょうね。
FORTRANのことが毎日少しずつ分かってきてる気がします。

FORMAT記述子 F E

実数型のFORMATの記述子、F、Eについて使ってみます。
まずはFから。

takk@deb9:~$ cat disp.f
       REAL A
       A=123.45
       WRITE(*,11111) A
11111  FORMAT(F10.3)
       STOP
       END
takk@deb9:~$ 

F10.3は、小数点を含んだ全部の桁数が10で、小数点以下の桁数が3という意味です。実行すると、このように表示されます。

takk@deb9:~$ gfortran !$
gfortran disp.f
takk@deb9:~$ ./a.out
   123.450
takk@deb9:~$

Fで指定する桁数が不足している場合は、値の代わりに*が表示されます。

takk@deb9:~$ cat disp.f
       REAL A
       A=123.45
       WRITE(*,11111) A
11111  FORMAT(F6.3)
       STOP
       END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
******
takk@deb9:~$

上記ではF6.3なので、123.450と表示が期待されますが、全桁数は6なので1不足しており、*の表示になっています。
F7.3にすると以下のように表示されます。

takk@deb9:~$ cat disp.f
       REAL A
       A=123.45
       WRITE(*,11111) A
11111  FORMAT(F7.3)
       STOP
       END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
123.450
takk@deb9:~$

次はEです。指数表記です。E全体桁数.小数点以下桁数で指定します。

takk@deb9:~$ cat disp.f
       REAL A
       A=123.45
       WRITE(*,11111) A
11111  FORMAT(E10.2)
       STOP
       END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
  0.12E+03
takk@deb9:~$

123.45は、指数表記で、0.12 x 10の3乗 なので、0.12E+03と表示されました。

FORMAT記述子G

FORMATの記述子Gを使います。

Gは、自動で判別して、FまたはEの書式で表示します。

takk@deb9:~$ cat disp.f
       REAL A
       A=123.45
       WRITE(*,11111) A
       WRITE(*,22222) A
       WRITE(*,33333) A
       WRITE(*,44444) A
11111  FORMAT(G10.5)
22222  FORMAT(g10.4)
33333  FORMAT(g10.3)
44444  FORMAT(g10.2)
       STOP
       END
takk@deb9:~$

実行結果です。

takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
123.45
 123.4
  123.
  0.12E+03
takk@deb9:~/gf$

C言語のprintfの書式にも%Gがありますが、素通りしてました。今回初めて意味を知りました。なぜGという文字を使うのかは謎です。

C書式に対応しているprintfコマンドで確認してみます。

takk@deb9:~$ printf "%10.5g\n" 123.45
    123.45
takk@deb9:~$

FORTRANのFORMATよりも分かりやすいです。全体を10桁右詰で表示し、仮数部を5桁で表示してますね。
printfを知ってしまったら、FORTRANの方のG10.5の10はいったい何の意味があるのでしょう、と疑問。printfを10.5gから10.2gまでの結果を並べてみてみます。

takk@deb9:~$ printf "%10.5g\n" 123.45
    123.45
takk@deb9:~$ printf "%10.4g\n" 123.45
     123.4
takk@deb9:~$ printf "%10.3g\n" 123.45
       123
takk@deb9:~$ printf "%10.2g\n" 123.45
   1.2e+02
takk@deb9:~$

FORTRANのFORMATで 0.12E+03 だったのが、printfでは 1.2e+02 と表示されました。
printfはてっきりFORTRANを参考にしたとばかり思ってましたが、どうなんでしょう。

FORMAT記述子F 数学チックな書き方

takk@deb9:~$ cat disp.f
      A=123.45
      B=250
      C=300.9
      WRITE(*,11111) A,B,C
11111 FORMAT(F7.2,F7.2,F7.2)
      STOP
      END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
 123.45 250.00 300.90
takk@deb9:~$

F7.2の記述は、なんと、まとめることができます。
このようにFの前に添え字(3)をつけると、同じ指定子が添え字で指定した数だけ繰り返して書式設定されます。

takk@deb9:~$ cat disp.f
      A=123.45
      B=250
      C=300.9
      WRITE(*,11111) A,B,C
11111 FORMAT(3F7.2)
      STOP
      END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
 123.45 250.00 300.90
takk@deb9:~$

驚きです。かなり数学チックな書き方かと思います。

次は各値を改行して表示してみます。まずは3回記述をする方法。

takk@deb9:~$ cat disp.f
      A=123.45
      B=250
      C=300.9
      WRITE(*,11111) A,B,C
11111 FORMAT(F7.2/F7.2/F7.2/)
      STOP
      END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
 123.45
 250.00
 300.90

takk@deb9:~$

添え字に3を指定して記述子を一つにまとめたいと思います。

takk@deb9:~$ cat disp.f
      A=123.45
      B=250
      C=300.9
      WRITE(*,11111) A,B,C
11111 FORMAT(3F7.2/)
      STOP
      END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
 123.45 250.00 300.90

takk@deb9:~$

一行で表示されていました。
実は添え字の3が適用されているのは、F7.2までなので、最後の一回だけ改行が表示されているんです。各値の表示後に改行するには、添え字の適用範囲を()で囲えばよいです。

takk@deb9:~$ cat disp.f
      A=123.45
      B=250
      C=300.9
      WRITE(*,11111) A,B,C
11111 FORMAT(3(F7.2/))
      STOP
      END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
 123.45
 250.00
 300.90

takk@deb9:~$

Fortran 変数

変数 1

私が参考にしてる1993年頃のFORTRAN入門書には変数名の規則として、「英字で始まる6文字以内」と書かれています。当時はそうだったのかもしれませんが、今はもっと長い名前が使えるようです。

takk@deb9:~$ cat var.f
      PROGRAM MAIN
      A23456789=123.45
      WRITE(*,11111) A23456789
11111 FORMAT(G10.5)
      STOP
      END
takk@deb9:~$ gfortran var.f
takk@deb9:~$ ./a.out
123.45
takk@deb9:~$

変数名の長さがどのぐらいまでいけるのか簡単なスクリプトで確認してみます。

takk@deb9:~$ cat var.pl
foreach $len ($ARGV[0]..$ARGV[1]){
$varname="A" x $len;
open(FH,">test.f");

print FH <<EOL;
      PROGRAM MAIN
      $varname=123.45
      WRITE(*,11111) $varname
11111 FORMAT(F10.2)
      STOP
      END
EOL

close(FH);
unlink "./a.out" if(-f "./a.out");
system("gfortran test.f");

print "var length=$len: ";

open(FH,"./a.out |");
print <FH>;
close(FH);
}
takk@deb9:~$

指定した変数名の長さのFORTRANプログラムtest.fを生成して、ビルド後、実行するスクリプトになっています。
引数には確認したい変数の長さ(開始、終了)を指定します。
まずは1文字の変数の確認をしてみましょう。

takk@deb9:~$ perl var.pl 1 1
var length=1:     123.45
takk@deb9:~$ 

Aという1文字の長さの変数を使ったプログラムを生成し、ビルドして実行しました。
最後にビルドしたソースは残ってますので確認してみます。

takk@deb9:~$ cat test.f
      PROGRAM MAIN
      A=123.45
      WRITE(*,11111) A
11111 FORMAT(F10.2)
      STOP
      END
takk@deb9:~$

次は2文字から10文字ぐらいを確認してみます。

takk@deb9:~$ perl var.pl 2 10
var length=2:     123.45
var length=3:     123.45
var length=4:     123.45
var length=5:     123.45
var length=6:     123.45
var length=7:     123.45
var length=8:     123.45
var length=9:     123.45
var length=10:     123.45
takk@deb9:~$ cat test.f
      PROGRAM MAIN
      AAAAAAAAAA=123.45
      WRITE(*,11111) AAAAAAAAAA
11111 FORMAT(F10.2)
      STOP
      END
takk@deb9:~$

10文字ぐらいは余裕で大丈夫ですね。
次は変数の長さを100文字にして確認してみましょう。

var length=100: takk@deb9:~$ perl var.pl 100 100
test.f:2:70:

      AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=123.45
                                                                     1
Error: Name at (1) is too long
var length=100: takk@deb9:~$

あれ、100文字ではビルド通りませんね。
半分の50文字を確認してみます。

var length=100: takk@deb9:~$ perl var.pl 50 50
var length=50:     123.45
takk@deb9:~$

通りました。結果も合ってます。ではその半分の75文字をみていきます。

takk@deb9:~$ perl var.pl 75 75
test.f:2:70:

      AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=123.45
                                                                     1
Error: Name at (1) is too long
var length=75: takk@deb9:~$

面倒なので51から74まで一気に確認します。

var length=75: takk@deb9:~$ perl var.pl 51 74
var length=51:     123.45
var length=52:       0.00
var length=53:       0.00
var length=54:      51.03
var length=55: **********
var length=56: **********
var length=57:       0.00
var length=58:      -0.00
var length=59: **********
var length=60:       0.00
var length=61: **********
var length=62: **********
var length=63: **********
test.f:2:70:

       AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=123.45
                                                                      1
Error: Name at (1) is too long
var length=64: test.f:2:70:

64文字でビルドエラーになりましたが、その前から結果が怪しくなっています。
まあ、あまり長い名前にはしない方が良いということですね。

ちなみに、 変数名は英小文字も使えるようですが、大文字と区別されないようです。

takk@deb9:~$ cat var.f
      PROGRAM MAIN
      A23456789=123.45
      a23456789=20
      WRITE(*,11111) A23456789
      WRITE(*,11111) a23456789
11111 FORMAT(G10.5)
      STOP
      END
takk@deb9:~$ gfortran var.f
takk@deb9:~$ ./a.out
20.000
20.000
takk@deb9:~$

変数 2 配列

配列は、変数名(添え字) で、定義します。BASICと同じですね。まあ、BASICはFORTRANの文法が基になっているようですので当然ですね。配列の添え字は1始まりです。

takk@deb9:~$ cat var.f
      PROGRAM MAIN
      INTEGER A(5)
      A(1) = 10
      A(2) = 20
      A(3) = 30
      A(4) = 40
      A(5) = 50
      WRITE(*,11111) A(3)
11111 FORMAT(I10)
      STOP
      END
takk@deb9:~$ gfortran var.f
takk@deb9:~$ ./a.out
        30
takk@deb9:~$

配列はすんなり理解できそうなので、次に繰り返しをしてみます。
DO~CONTINUEを使います。

takk@deb9:~$ cat for.f
      PROGRAM MAIN
      INTEGER A(5)
      A(1) = 10
      A(2) = 20
      A(3) = 30
      A(4) = 40
      A(5) = 50
      DO 11111 I = 1,5
          WRITE(*,22222) A(I)
11111 CONTINUE
22222 FORMAT(I10)
      STOP
      END
takk@deb9:~$ gfortran for.f
takk@deb9:~$ ./a.out
        10
        20
        30
        40
        50
takk@deb9:~$

DOで指定するのは、CONTINUEがある行のラベル名と、繰り返しの範囲です。
ラベルは必要ないような気もしますが、こんなこともできます。

takk@deb9:~$ cat for.f
      PROGRAM MAIN
      DO 11111 I = 1,2
         DO 11111 J = 1,3
             WRITE(*,22222) I,J
11111 CONTINUE
22222 FORMAT(2I5)
      STOP
      END
takk@deb9:~$ gfortran for.f
takk@deb9:~$ ./a.out
    1    1
    1    2
    1    3
    2    1
    2    2
    2    3
takk@deb9:~$

少し感動しました。FORTRANの学習を初めてよかったなあ、と。
BASICのFOR~NEXTでは、NEXTは2か所必要でしたが、FORTRANではまとめて書くことができるんです。
DOの行も範囲をすっきり指定できますし、優れた文法を持っているんですね。

あと、忘れていましたが、変数Iと変数Jについて、いつのまにか整数型になっています。
実は、I~Nで始まる変数名は、デフォルトで整数型になります。(これ以外は実数型)

takk@deb9:~$ cat disp.f
      I=100
      J=200
      K=200
      L=200
      M=200
      N=200
      WRITE(*,11111) I,J,K,L,M,N
11111 FORMAT(6(I5/))
      STOP
      END
takk@deb9:~$ gfortran disp.f
takk@deb9:~$ ./a.out
  100
  200
  200
  200
  200
  200

takk@deb9:~$

思いもよらない変数名の仕様です。古い言語ですが、新しい発見があって面白いです。

Fortran READ

READ 1

FORTRAN、少しずつ学習しているうちに面白くなってきた気がします。
READの学習です。キーボードから入力するには、第1引数に*アスタリスクを指定します。第2引数は、FORMAT文がある行のラベルを指定します。

takk@deb9:~$ cat read.f
      PROGRAM MAIN
      READ (*,11111) I
      WRITE(*,22222) I
11111 FORMAT(I5)
22222 FORMAT('I =',I5)
      STOP
      END

READ文の行でキーボードからの入力待ちとなります。入力した数字が、変数Iに格納されます。
実行してみます。

takk@deb9:~$ gfortran read.f
takk@deb9:~$ ./a.out
333
I =  333
takk@deb9:~$

複数の数字を入力するプログラムに改造してみます。
きっとカンマで区切って指定すればよいかと思います。

takk@deb9:~$ cat read.f
      PROGRAM MAIN
      READ (*,11111) I,J
      WRITE(*,22222) I,J
11111 FORMAT(I5,I5)
22222 FORMAT('I =',I5,',J=',I5)
      STOP
      END
takk@deb9:~$ gfortran read.f
takk@deb9:~$ ./a.out
100
I =  100,J=    0
takk@deb9:~$

なんてことでしょう。2つの変数に入力したいのに、改行を押したとたん処理が進んでしまいました。
2回に分けないといけないのでしょうか。

takk@deb9:~$ cat read2.f
      PROGRAM MAIN
      READ (*,11111) I
      READ (*,11111) J
      WRITE(*,22222) I,J
11111 FORMAT(I5)
22222 FORMAT('I =',I5,',J=',I5)
      STOP
      END
takk@deb9:~$ gfortran read2.f
takk@deb9:~$ ./a.out
100
200
I =  100,J=  200
takk@deb9:~$

確かにREADを2回に分けると確実ですね。WRITEがカンマで区切って変数指定できるのに、なぜでしょう。
ここ数日の学習内容を見返して改行について思い出しました。FORMATをカンマの代わりに/を使えば改行になるんでしたね。使ってみます。

takk@deb9:~$ cat read.f
      PROGRAM MAIN
      READ (*,11111) I,J
      WRITE(*,22222) I,J
11111 FORMAT(I5/I5)
22222 FORMAT('I =',I5,',J=',I5)
      STOP
      END
takk@deb9:~$ gfortran read.f
takk@deb9:~$ ./a.out
100
200
I =  100,J=  200
takk@deb9:~$

感でしたが、動きました。直感で使える言語というのは優れた言語であろうと思いますが、FORTRANもそうなんですね。

READ 2 おさらい

READのおさらいです。
キーボードから改行で区切った2値を入力して2つの変数へ代入するには、FORMATで/を使えばよかったですが、

      READ (*,11111) I,J
11111 FORMAT(I5/I5)
takk@deb9:~$ ./a.out
20
30
I =   20,J=   30
takk@deb9:~$

,(カンマ)で区切った場合、入力できないと思い込んでいましたができました。
キーボードから入力する値を,(カンマ)で区切ればよいだけでした。

      READ (*,11111) I,J
11111 FORMAT(I5,I5)
takk@deb9:~$ ./a.out
20,30
I =   20,J=   30
takk@deb9:~$

実はREAD文だけで、FORMATも必要ありません。

takk@deb9:~$ cat read.f
      PROGRAM MAIN
      READ (*,*) I,J
      WRITE(*,22222) I,J
22222 FORMAT('I =',I5,',J=',I5)
      STOP
      END
takk@deb9:~$ gfortran read.f
takk@deb9:~$ ./a.out
20,30
I =   20,J=   30
takk@deb9:~$

このように、READの第2引数も*にしておけば、,(カンマ)で区切った数値を各変数に代入できます。そしてこの方法のすばらしさが、改行にも使えるということ。

takk@deb9:~$ ./a.out
20
30
I =   20,J=   30
takk@deb9:~$

READはFORMAT要らないですね。
,(カンマ)や、Enterキーの入力が面倒な場合は、スペースキーで区切ることもできます。

takk@deb9:~$ ./a.out
20 30
I =   20,J=   30
takk@deb9:~$

,とEnterの混在でも認識してくれます。

takk@deb9:~$ ./a.out
20,
30
I =   20,J=   30
takk@deb9:~$

指定された変数以上に値がある場合はどうなるでしょうか。

takk@deb9:~$ ./a.out
20,30,40
I =   20,J=   30
takk@deb9:~$

最初の2値のみ代入されたようです。

よくありがちなプログラミング言語のif文は、こんな形かと思います。

if 条件 then
else
end if

FORTRAN(77の昔の教科書を見ています)は当然元祖なので、この形になります。しかし実際のプログラムを見てみると戸惑います。

IF (X.EQ.1) THEN
ELSE
ENDIF

括弧の中の、X.EQ.1という記述。何が書いてあるのか分かりませんでした。学生時代にもきっと学習した筈なんですが。まったく意味が分かりません。
しかし、.(ピリオド)を空白にしてもう一度見てみると、

X EQ 1

ああ、Xが1と同値の時か。案外簡単でした。ピリオドが空白になるだけで、だいぶ違って見えます。ピリオド=メンバやメソッドという固定観念が植えられてしまっているので、X.EQ.1の様な表記だとどうしても頭に入ってこないんです。でも、一回謎が解ければもう簡単です。

条件式の書き方が分かったところで、数字を入力して20より上かどうか判定するプログラムを作ってみます。

takk@deb9:~$ cat if.f
      PROGRAM MAIN
      READ (*,*) I
      IF(I.GT.20) THEN
          WRITE(*,11111)
      ELSE
          WRITE(*,22222)
      ENDIF
11111 FORMAT('I  > 20')
22222 FORMAT('I =< 20')
      STOP
      END
takk@deb9:~$

.GT.は、Greater Than(より大きい)の略です。

実行します。

takk@deb9:~$ gfortran if.f
takk@deb9:~$ ./a.out
21
I  > 20
takk@deb9:~$ ./a.out
20
I =< 20
takk@deb9:~$

条件式には以下のものがあります。

A .GT. B	Greater Than ( A > B )
A .LT. B	Less Than ( A < B )
A .GE. B	Greater than or Equal to ( A >= B )
A .LE. B	Less than or Equal to ( A <= B )
A .EQ. B	EQual to ( A == B )
A .NE. B	Not Equal to ( A != B )
A .NOT. B	not ( A not B )
A .AND. B	and ( A and B )
A .OR. B	or ( A or B )

では、もう少し複雑な式にします。20より大きく30以外の場合のみ表示するプログラムを作ります。

takk@deb9:~$ cat if.f
      PROGRAM MAIN
      READ (*,*) I
      IF((I.GT.20).AND.(I.NE.30)) THEN
          WRITE(*,11111)
      ENDIF
11111 FORMAT('I  > 20 not 30')
      STOP
      END
takk@deb9:~$ 

実行結果です

takk@deb9:~$ gfortran if.f
takk@deb9:~$ ./a.out
30
takk@deb9:~$ ./a.out
29
I  > 20 not 30
takk@deb9:~$

Fortran 関数

サブルーチン

関数は、FUNCTIONですが、まずはサブルーチンから使います。サブルーチンを作るには、SUBROUTINEを使います。

takk@deb9:~$ cat sub.f
      PROGRAM MAIN
      CALL HELLO
      STOP
      END

      SUBROUTINE HELLO()
      WRITE(*,11111)
11111 FORMAT('HELLO')
      RETURN
      END

takk@deb9:~$

サブルーチンHELLOを実行するには、CALLが必要みたいです。そしてVBのSUBの元はSUBROUTINEだったということも分かりました。
ではプログラムを実行してみます。

takk@deb9:~$ gfortran sub.f
takk@deb9:~$ ./a.out
HELLO
takk@deb9:~$

次は関数です。関数はFUNCTIONで定義します。戻り値を設定するには、関数名に代入してやればよいです。VBと同じですね。

takk@deb9:~$ cat func.f
      PROGRAM MAIN
      READ (*,*) I,J
      K=ADD(I,J)
      WRITE(*,11111) K
11111 FORMAT(I5)
      STOP
      END

      FUNCTION ADD(I,J)
         ADD = I + J
         RETURN
      END

takk@deb9:~$

実行結果です。

takk@deb9:~$ gfortran func.f
takk@deb9:~$ ./a.out
10,20
   30
takk@deb9:~$

次は、またサブルーチンですが、引数ありのサブルーチンです。

takk@deb9:~$ cat sub.f
      PROGRAM MAIN
      READ(*,*) I,J
      CALL ADD(I,J,K)
      WRITE(*,11111) K
11111 FORMAT(I5)
      STOP
      END

      SUBROUTINE ADD(I,J,K)
      K = I + J
      RETURN
      END
takk@deb9:~$ 

これはスゴイです。C言語ばかりやってきた私としては、ポインタや参照を使わないのに引数で戻り値を返せるというところにびっくり。いろんな言語の祖先だけあってFORTRANは結構柔軟にできてるんですね。
実行結果です。

takk@deb9:~$ gfortran sub.f
takk@deb9:~$ ./a.out
11,22
   33
takk@deb9:~$

関数

組み込み関数を使用して計算してみます。

takk@deb9:~$ cat math.f
      PROGRAM MAIN
      READ (*,*) A
      B=SQRT(A)
      WRITE(*,11111) B
11111 FORMAT(F5.2)
      STOP
      END

takk@deb9:~$ 
takk@deb9:~$ gfortran math.f
takk@deb9:~$ ./a.out
25
 5.00
takk@deb9:~$ ./a.out
3
 1.73
takk@deb9:~$ ./a.out
5
 2.24
takk@deb9:~$

SQRTは平方根を求める関数ですが、他の言語だとして使う時にインポートしたりインクルードしたりいろいろ必要ですが、FORTRANの場合はそのまま使えます。

他の計算もしてみましょう。

takk@deb9:~$ cat math.f
      PROGRAM MAIN
      A=LOG(10.0)
      WRITE(*,11111) A
      A=LOG10(1000.0)
      WRITE(*,11111) A
      RAD=30 * (2 * 3.14 / 360)
      A=SIN(RAD)
      WRITE(*,11111) A
      A=COS(RAD)
      WRITE(*,11111) A
      A=TAN(RAD)
      WRITE(*,11111) A
11111 FORMAT(F5.2)
      STOP
      END

takk@deb9:~$ gfortran math.f
takk@deb9:~$ ./a.out
 2.30
 3.00
 0.50
 0.87
 0.58
takk@deb9:~$

文関数

FORTRANは偶然大文字で記述していましたが、新しいFortranは、もう言語が違うんじゃないかってぐらい古いFORTRANと異なっているらしくて、新旧区別するためにFORTRANとFortranというように記述を分けてるらしいです。偶然合ってましたが、知らずに全大文字表記を使ってました。

今回は、文関数を使います。
定義方法の違いを見るため、まず普通の関数(かつては副関数と呼ばれていた)をおさらいします。

      PROGRAM MAIN
      READ (*,*) I,J
      K=ADD(I,J)
      WRITE(*,11111) K
11111 FORMAT(I5)
      STOP
      END

      FUNCTION ADD(I,J)
         ADD = I + J
         RETURN
      END

文関数。

      PROGRAM MAIN
      ADD(I,J) = I + J
      READ (*,*) I,J
      K=ADD(I,J)
      WRITE(*,11111) K
11111 FORMAT(I5)
      STOP
      END

まるで違いますね。文関数はとても数学的に書けます。しかも簡単です。 何か作ってみましょう。

77では、円周率の定数が無いようですので、PIを返す文関数を作ってみます。

takk@deb9:~$ cat pi.f
      PROGRAM MAIN
      PI() = ATAN(1.0) * 4
      A=PI()
      WRITE(*,11111) A
11111 FORMAT(F10.5)
      STOP
      END
takk@deb9:~$ gfortran pi.f
takk@deb9:~$ ./a.out
   3.14159
takk@deb9:~$

Fortran ファイル書き込み

takk@deb9:~$ cat write-file.f
      PROGRAM MAIN
      OPEN(UNIT=10,FILE='sample.txt',STATUS='NEW',
     1ACCESS='SEQUENTIAL')
      I = 110
      J = 220
      K = 330
      WRITE(10,*) I,J,K
      CLOSE(10,STATUS='KEEP')
      STOP
      END
takk@deb9:~$ 

OPEN関数の各引数には、キーワードがついています。Pythonのキーワード引数と似てますね。驚きです。FORTRAN77の頃からこの指定方法があったんですね。
OPENの次の行に、1ACCESSとありますが、1は前行の続きの意味です。空白以外ならOKですが、続きの行数がわかるように1と書いています。
実行してみましょう。

takk@deb9:~$ gfortran write-file.f
takk@deb9:~$ ./a.out
takk@deb9:~$ cat sample.txt
         110         220         330
takk@deb9:~$

sample.txtにデータが保存されました。こんな書式で格納されるんですね。
それにしても引数のキーワードって分かりやすいですね。
つけていれば分かりやすいですが、行をまたぐのは嬉しくないので、引数の各キーワードが省略できるか確認してみます。

takk@deb9:~$ cat write-file2.f
      PROGRAM MAIN
      OPEN(10,'sample.txt','NEW','SEQUENTIAL')
      I = 110
      J = 220
      K = 330
      WRITE(10,*) I,J,K
      CLOSE(10,STATUS='KEEP')
      STOP
      END
takk@deb9:~$ gfortran write-file2.f
write-file2.f:2:14:

       OPEN(10,'sample.txt','NEW','SEQUENTIAL')
              1
Error: Syntax error in OPEN statement at (1)
takk@deb9:~$

ダメでした。エラーになりました。

省略できるのは、先頭のUNITのみのようです。

takk@deb9:~$ cat write-file3.f
      PROGRAM MAIN
      OPEN(10,FILE='sample.txt',STATUS='NEW',ACCESS='SEQUENTIAL')
      I = 110
      J = 220
      K = 330
      WRITE(10,*) I,J,K
      CLOSE(10,STATUS='KEEP')
      STOP
      END
takk@deb9:~$ gfortran write-file3.f
takk@deb9:~$ ./a.out
takk@deb9:~$ cat sample.txt
         110         220         330
takk@deb9:~$

Fortran ファイル読み込み

ファイルを読み込みしてみます。
catで見るとこのようなデータになっています。

takk@deb9:~$ cat sample.txt
         110         220         330
takk@deb9:~$

読み込みのプログラムは書き込みプログラムをWRITEからREADに変えればよいです。その際、OPENのSTATUSはOLDにします。OLDという表現が妥当かどうか分かりませんが、既存ファイルの意味です。

takk@deb9:~$ cat read-file.f
      PROGRAM MAIN
      OPEN(UNIT=10,FILE='sample.txt',STATUS='OLD',
     1ACCESS='SEQUENTIAL')
      READ (10,*) I,J,K
      WRITE(*,22222) I,J,K
      CLOSE(10,STATUS='KEEP')
11111 FORMAT(I5)
22222 FORMAT('I =',I5,',J=',I5,',K=',I5)
      STOP
      END
takk@deb9:~$ 

実行すると各変数に各データが格納されていることが分かります。

takk@deb9:~$ gfortran read-file.f
takk@deb9:~$ ./a.out
I =  110,J=  220,K=  330
takk@deb9:~$

読み込みファイルのフォーマットは、どうやら空白がデータの区切り文字となっているようですが、空白の数はいくつでもよいのでしょうか。まずは一つから。

takk@deb9:~$ cat sample.txt
110 220 330
takk@deb9:~$ ./a.out
I =  110,J=  220,K=  330
takk@deb9:~$

空白一つでも問題ないようです。

100個ぐらいの空白を入れて確認してみましょう。

takk@deb9:~$ perl -e '$s=" " x 100;print"110${s}220${s}330\n"' > sample.txt
takk@deb9:~$ wc -L sample.txt
209 sample.txt
takk@deb9:~$

空白100 * 2 + 3文字 * 3 = 209なので、サイズは一致しますね。
実行してみます。

takk@deb9:~$ ./a.out
I =  110,J=  220,K=  330
takk@deb9:~$

問題なく読み込みできました。

読み込む時に指定する書式が間違っているとどうなるのでしょうか。

takk@deb9:~$ cat read-file-f.f
      PROGRAM MAIN
      OPEN(UNIT=10,FILE='sample.txt',STATUS='OLD',
     1ACCESS='SEQUENTIAL')
      READ (10,11111) I,J,K
      WRITE(*,22222) I,J,K
      CLOSE(10,STATUS='KEEP')
11111 FORMAT(3I5)
22222 FORMAT('I =',I5,',J=',I5,',K=',I5)
      STOP
      END
takk@deb9:~$ gfortran read-file-f.f
takk@deb9:~$ cat sample.txt
110 220 330
takk@deb9:~$ ./a.out
I = 1102,J= 2033,K=    0

空白を無視して読み込まれました。なかなか強引ですね。

Fortran 文字列

文字列

FORTRAN77のことは分かってきたつもりですが、いまだにまったく使えていないのが文字列です。プログラマーの感を働かせても使い方がまったく分かりません。
相変わらず古い教科書が頼りです。

いまどきの言語なら、文字列はa = ‘hello’とやるだけで使えてしまいますが、FORTRAN77では、宣言が必要です。しかも文字列のサイズも指定してやる必要があります。

宣言の方法は、
CHARACTER*サイズ 変数名
です。
このように使います。

takk@deb9:~$ cat string.f
      PROGRAM MAIN
      CHARACTER*3 S
      S='ABC'
      WRITE(*,11111) S
11111 FORMAT(A5)
      STOP
      END
takk@deb9:~$ gfortran string.f
takk@deb9:~$ ./a.out
  ABC
takk@deb9:~$

初めて出てきましたが、FORMATで文字列を指定する場合は、Aを使います。

次は文字列結合します。

文字列結合は、
変数C = 変数A//変数B
で、できます。

最初に動かないプログラムを見ます。

takk@deb9:~$ cat string.f
      PROGRAM MAIN
      CHARACTER*10 S1,S2,S
      S1='ABC'
      S2='DEF'
      S=S1//S2
      WRITE(*,11111) S
11111 FORMAT(A10)
      STOP
      END
takk@deb9:~$ gfortran string.f
takk@deb9:~$ ./a.out
ABC
takk@deb9:~$

S=S1//S2で結合しているはずなのに、ABCしか表示されません。
今はまだ知識不足で原因不明なのですが、こうすれば動きました。

takk@deb9:~$ cat string.f
      PROGRAM MAIN
      CHARACTER*3 S1,S2,S*10
      S1='ABC'
      S2='DEF'
      S=S1//S2
      WRITE(*,11111) S
11111 FORMAT(A10)
      STOP
      END
takk@deb9:~$ gfortran string.f
takk@deb9:~$ ./a.out
ABCDEF
takk@deb9:~$

やはり古いプログラミング言語は、制約が多そうで覚えるのが難解です。
まあ、それが面白いんですが。

文字列 スライス

なんとFORTRAN77にも、文字列スライスがありました。

こんな風に使います。

文字列変数(最初:最後)

takk@deb9:~$ cat slice1.f
      PROGRAM MAIN
      CHARACTER*11 S
      S='ABCDE FGHIJ'
      WRITE(*,11111) S(1:5)
11111 FORMAT(A10)
      STOP
      END
takk@deb9:~$ gfortran slice1.f
takk@deb9:~$ ./a.out
     ABCDE
takk@deb9:~$

感動です。Pythonのスライスで感動を覚えたときもありましたが、FORTRAN77の時代からあったとは。やはり考古学は大事です。

:(コロン)から右を省略すると最大値が指定されます。

takk@deb9:~$ cat slice2.f
      PROGRAM MAIN
      CHARACTER*11 S
      S='ABCDE FGHIJ'
      WRITE(*,11111) S(7:)
11111 FORMAT(A10)
      STOP
      END
takk@deb9:~$ gfortran slice2.f
takk@deb9:~$ ./a.out
     FGHIJ
takk@deb9:~$

:(コロン)から左を省略すると最小値の指定となります。

takk@deb9:~$ cat slice3.f
      PROGRAM MAIN
      CHARACTER*11 S
      S='ABCDE FGHIJ'
      WRITE(*,11111) S(:5)
11111 FORMAT(A10)
      STOP
      END
takk@deb9:~$ gfortran slice3.f
takk@deb9:~$ ./a.out
     ABCDE
takk@deb9:~$

置換もできます。こんなことまでできるんですね。

takk@deb9:~$ cat slice4.f
      PROGRAM MAIN
      CHARACTER*11 S
      S='ABCDE FGHIJ'
      S(4:7)='4567'
      WRITE(*,11111) S
11111 FORMAT(A10)
      STOP
      END
takk@deb9:~$ gfortran slice4.f
takk@deb9:~$ ./a.out
ABC4567GHI
takk@deb9:~$

しかし置換する文字列の個数が合わないと、このように結果がおかしくなります。

takk@deb9:~$ cat slice5.f
      PROGRAM MAIN
      CHARACTER*11 S
      S='ABCDE FGHIJ'
      S(4:7)='12'
      WRITE(*,11111) S
11111 FORMAT(A10)
      STOP
      END
takk@deb9:~$ gfortran slice5.f
takk@deb9:~$ ./a.out
ABC12  GHI
takk@deb9:~$

標準入力、文字列トリム

FORTRAN続きで、標準入力です。
標準入力を使うには、READ(*,*)でよさそうです。標準入力した文字列を表示するプログラムを作ります。
100文字ぐらい読めるようにCHARACTER*100で定義しておきます。

takk@deb9:~$ cat stdin.f
      PROGRAM stdin
      CHARACTER*100 LINE

      READ(*,*,end=99999) LINE
      WRITE(*,*) LINE
99999 STOP
      END
takk@deb9:~$ gfortran stdin.f
takk@deb9:~$ echo HELLO| ./a.out
 HELLO                                                                          
takk@deb9:~$

複数行読めるでしょうか。

takk@deb9:~$ seq 10 | ./a.out
 1                                                                              

takk@deb9:~$

良く分かってないですが、やはり先頭の一行しか無理そうです。
複数行読むには、DOで回します。

takk@deb9:~$ cat stdin2.f
      PROGRAM stdin2
      CHARACTER*100 LINE

      DO
        READ(*,*,end=99999) LINE
        WRITE(*,*) LINE
      END DO
99999 STOP
      END
takk@deb9:~$ gfortran stdin2.f
takk@deb9:~$ seq 10 | ./a.out
 1                                                                              

 2                                                                              

 3                                                                              

 4                                                                              

 5                                                                              

 6                                                                              

 7                                                                              

 8                                                                              

 9                                                                              

 10                                                                             

takk@deb9:~$

出るには出たのですが、なぜ一つ飛びで表示されるのでしょう。
もしかして、*100となっているのが原因かも。端末の行数を超えてしまってるのかもしれません。
wcコマンドで確認してみます。

takk@deb9:~$ seq 10 | ./a.out | wc -L
101
takk@deb9:~$

間違いないです。改行を含めて101文字表示されています。
トリムしてみます。
文字列変数(最初,最後)で部分文字列が抽出できるので、文字列の長さが分かればいけるでしょうか。LENを使ってみます。

takk@deb9:~$ cat stdin3.f
      PROGRAM stdin3
      CHARACTER*100 LINE

      DO
        READ(*,*,end=99999) LINE
        WRITE(*,*) LEN(LINE)
      END DO
99999 STOP
      END
takk@deb9:~$ gfortran stdin3.f
takk@deb9:~$ seq 10 | ./a.out
         100
         100
         100
         100
         100
         100
         100
         100
         100
         100
takk@deb9:~$

だめでした。空白も含んだ文字列の長さが出てしまうので100と表示されます。当然でしたね。
次は、INDEXで空白の出現位置を取得してみます。

takk@deb9:~$ cat stdin4.f
      PROGRAM stdin4
      CHARACTER*100 LINE

      DO
        READ(*,*,end=99999) LINE
        WRITE(*,*) INDEX(LINE,' ')
      END DO
99999 STOP
      END
takk@deb9:~$ gfortran stdin4.f
takk@deb9:~$ seq 10 | ./a.out
           2
           2
           2
           2
           2
           2
           2
           2
           2
           3
takk@deb9:~$

大丈夫そうです。INDEXで空白の出現位置を確認して、トリムしたいと思います。

takk@deb9:~$ cat stdin5.f
      PROGRAM stdin5
      CHARACTER*100 LINE

      DO
        READ(*,*,end=99999) LINE
        WRITE(*,*) LINE(1:INDEX(LINE,' '))
      END DO
99999 STOP
      END
takk@deb9:~$
takk@deb9:~$ gfortran stdin5.f
takk@deb9:~$ seq 10 | ./a.out
 1
 2
 3
 4
 5
 6
 7
 8
 9
 10

できました。念のためカラム数を確認します。

takk@deb9:~$ seq 10 | ./a.out | wc -L
4

Fortran 変数の結合

また知らない仕様の発見です。長く学習している甲斐があります。
変数の結合というか、CのUNIONのような機能ですが、手元の古い77の教科書には結合とありました。配列要素の結合です。

takk@deb9:~$ cat equ1.f
      PROGRAM MAIN
      INTEGER A(5),B(5)
      EQUIVALENCE (A(1),B(1))
      A(1)=10
      WRITE(*,11111) B(1)
11111 FORMAT(I5)
      STOP
      END
takk@deb9:~$ gfortran equ1.f
takk@deb9:~$ ./a.out
   10
takk@deb9:~$

EQUIVALENCEにて配列Aと配列Bを結合しました。
Aに値を入れると、Bでも読み出せるようになります。もちろんBに値を入れてAから読み出すこともできます。シンボリックリンクみたいです。

結合する配列の位置をずらすこともできます。

takk@deb9:~$ cat equ2.f
      PROGRAM MAIN
      INTEGER A(5),B(5)
      EQUIVALENCE (A(3),B(1))
      A(3)=10
      A(4)=20
      A(5)=30
      WRITE(*,11111) B(1),B(2),B(3)
11111 FORMAT(3I5)
      STOP
      END
takk@deb9:~$ gfortran equ2.f
takk@deb9:~$ ./a.out
   10   20   30
takk@deb9:~$

上のようにA(3)とB(1)をEQUIVALENCEで結びつけると、B(2)以降もAの対応する配列と結合されます。

文字列配列も同じようにできます。結合する変数の型は合わせないといけません。

takk@deb9:~$ cat equ3.f
      PROGRAM MAIN
      CHARACTER*8 A(3),B(3)
      EQUIVALENCE (A(1),B(2))
      A(1)='111'
      A(2)='222'
      A(3)='333'
      WRITE(*,11111) B(3)
11111 FORMAT(A10)
      STOP
      END
takk@deb9:~$ gfortran equ3.f
takk@deb9:~$ ./a.out
  222
takk@deb9:~$

配列変数の結合は、変数2つ限定ではなく、多数(複数)できます。

takk@deb9:~$ cat equ4.f
      PROGRAM MAIN
      INTEGER A(5),B(5),C(5)
      EQUIVALENCE (A(1),B(2),C(3))
      A(1)=10
      A(2)=20
      A(3)=30
      WRITE(*,11111) B(3),C(5)
11111 FORMAT(2I5)
      STOP
      END
takk@deb9:~$ gfortran equ4.f
takk@deb9:~$ ./a.out
   20   30
takk@deb9:~$

Fortran seqコマンドを作る

seq 1

seqコマンドを作っていきます。まずはコマンド引数を取得するFORTRAN77プログラムです。

takk@deb9:~$ cat arg.f
      PROGRAM GETARGUMENTS
      CHARACTER*20 ARG0,ARG1,ARG2,ARG3
      CALL GETARG(0,ARG0)
      CALL GETARG(1,ARG1)
      CALL GETARG(2,ARG2)
      CALL GETARG(3,ARG3)
      WRITE(*,10) ARG0
      WRITE(*,10) ARG1
      WRITE(*,10) ARG2
      WRITE(*,10) ARG3
   10 FORMAT(A)
      STOP
      END
takk@deb9:~$

実行すると、

takk@deb9:~$ gfortran arg.f
takk@deb9:~$ ./a.out a b c
./a.out
a
b
c
takk@deb9:~$ 

ARG0にコマンドそのものの文字列が格納されます。C言語と同じです。ARG1~にパラメータが格納されます。例ではARG3までですが、パラメータ数が取得できるので、DOで回して処理すればよいです。
コマンドの引数の取得方法が分かりましたので、seqコマンドを作っていきます。まずは引数一つの場合のプログラムを作る前に、seq 10の固定結果が返るプログラムを作ってみます。

takk@deb9:~/gf$ cat seq.f
      PROGRAM seq
      DO 11111 I = 1, 10
11111   WRITE(*,22222) I
      CONTINUE
22222 FORMAT(I3)
      STOP
      END
takk@deb9:~$ gfortran seq.f
takk@deb9:~$ ./a.out
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
takk@deb9:~$

うまくいきました。DO行の10をコマンド引数の数字の変数にすり替えれば、作れそうです。はい。そうして作ったプログラムがこれです。

takk@deb9:~$ cat seq.f
      PROGRAM seq
      CHARACTER*10 ARG1
      CALL GETARG(1,ARG1)
      READ(ARG1,*) K
      DO 11111 I = 1, K
11111   WRITE(*,22222) I
      CONTINUE
22222 FORMAT(I3)
      STOP
      END
takk@deb9:~$

実行結果です。

takk@deb9:~$ gfortran seq.f
takk@deb9:~$ ./a.out 3
  1
  2
  3
takk@deb9:~$ ./a.out 5
  1
  2
  3
  4
  5
takk@deb9:~$ ./a.out 10
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
takk@deb9:~$

seq 2

パラメータ数によりふるまいを変えてみます。
コマンドのパラメータ数を取得するには、IARGC()関数を使います。関数なのでCALLは不要です。

takk@deb9:~$ cat argc.f
      PROGRAM argument_num
      I = IARGC()
11111 WRITE(*,22222) I
22222 FORMAT(I3)
      STOP
      END
takk@deb9:~$

上のプログラムの実行結果です。

takk@deb9:~$ gfortran argc.f
takk@deb9:~$ ./a.out a
  1
takk@deb9:~$ ./a.out a b c d
  4
takk@deb9:~$ ./a.out
  0
takk@deb9:~$

パラメータ数でふるまいを変えるには、IF文を使います。77の頃はCASEがなかったようです。
パラメータなしの場合、1,2,3の場合、4つ以上の場合のふるまいを変えるプログラムです。

takk@deb9:~$ cat select.f
      PROGRAM argument_num
      I = IARGC()
      IF(I.EQ.0) THEN
        WRITE(*,10000)
      END IF
      IF(I.EQ.1) THEN
        WRITE(*,10001)
      END IF
      IF(I.EQ.2) THEN
        WRITE(*,10002)
      END IF
      IF(I.EQ.3) THEN
        WRITE(*,10003)
      END IF
      IF(I.GT.3) THEN
        WRITE(*,10004)
      END IF
10000 FORMAT('NOTHING ')
10001 FORMAT('PARAMETER NUM=1')
10002 FORMAT('PARAMETER NUM=2')
10003 FORMAT('PARAMETER NUM=3')
10004 FORMAT('OVER')
      STOP
      END
takk@deb9:~$

実行結果です。

takk@deb9:~$ gfortran select.f
takk@deb9:~$ ./a.out
NOTHING
takk@deb9:~$ ./a.out aa
PARAMETER NUM=1
takk@deb9:~$ ./a.out aa bb
PARAMETER NUM=2
takk@deb9:~$ ./a.out aa bb cc
PARAMETER NUM=3
takk@deb9:~$ ./a.out aa bb cc dd
OVER
takk@deb9:~$

seq 3

今まで学習したことを組み合わせてパラメータを3つまで対応するseqコマンドを作ります。
まずは何も考えずに5パターンのプログラムを作ります。

takk@deb9:~$ cat seq.f
      PROGRAM seq
      INTEGER FIRST,STEP,LAST
      CHARACTER*10 ARG1,ARG2,ARG3

      FIRST = 1
      STEP = 1

      I = IARGC()

      IF(I.EQ.0) THEN
        WRITE(*,99999)
      END IF

      IF(I.EQ.1) THEN
        CALL GETARG(1,ARG3)
        READ(ARG3,*) LAST
      END IF

      IF(I.EQ.2) THEN
        CALL GETARG(1,ARG1)
        CALL GETARG(2,ARG3)
        READ(ARG1,*) FIRST
        READ(ARG3,*) LAST
      END IF

      IF(I.EQ.3) THEN
        CALL GETARG(1,ARG1)
        CALL GETARG(2,ARG2)
        CALL GETARG(3,ARG3)
        READ(ARG1,*) FIRST
        READ(ARG2,*) STEP
        READ(ARG3,*) LAST
      END IF

      IF(I.GT.3) THEN
        WRITE(*,99999)
      END IF
      CALL GETARG(1,ARG1)
      READ(ARG1,*) K

      DO 11111 I = FIRST, LAST, STEP
11111   WRITE(*,10000) I
      CONTINUE

10000 FORMAT(I5)
99999 FORMAT('ERROR')
      STOP
      END
takk@deb9:~$
takk@deb9:~$ gfortran seq.f
takk@deb9:~$ ./a.out 10
    1
    2
    3
    4
    5
    6
    7
    8
    9
   10
takk@deb9:~$ ./a.out 1 5
    1
    2
    3
    4
    5
takk@deb9:~$ ./a.out 2 5
    2
    3
    4
    5
takk@deb9:~$ ./a.out 1 2 10
    1
    3
    5
    7
    9
takk@deb9:~$ 

引数があるときは動きましたが、引数なしだとエラーになります。

takk@deb9:~$ ./a.out
ERROR
At line 39 of file seq.f
Fortran runtime error: End of file

Error termination. Backtrace:
#0  0x7fa01deb6d4a
#1  0x7fa01deb7825
#2  0x7fa01deb7f79
#3  0x7fa01df7adc3
#4  0x7fa01df75c51
#5  0x564e009d2029
#6  0x564e009d2162
#7  0x7fa01d3c22b0
#8  0x564e009d19a9
#9  0xffffffffffffffff
takk@deb9:~$

引数チェックをするように変更します。パラメータが1~3の範囲内である場合のみ処理することにします。

takk@deb9:~$ cat seq.f
      PROGRAM seq
      INTEGER FIRST,STEP,LAST
      CHARACTER*10 ARG1,ARG2,ARG3

      FIRST = 1
      STEP = 1

      I = IARGC()

      IF((I.LT.1).OR.(I.GT.3)) THEN
          WRITE(*,99999)
          GOTO 99998
      ELSE IF(I.EQ.1) THEN
          CALL GETARG(1,ARG3)
          READ(ARG3,*) LAST
      ELSE IF(I.EQ.2) THEN
          CALL GETARG(1,ARG1)
          CALL GETARG(2,ARG3)
          READ(ARG1,*) FIRST
          READ(ARG3,*) LAST
      ELSE IF(I.EQ.3) THEN
          CALL GETARG(1,ARG1)
          CALL GETARG(2,ARG2)
          CALL GETARG(3,ARG3)
          READ(ARG1,*) FIRST
          READ(ARG2,*) STEP
          READ(ARG3,*) LAST
      END IF

      DO 11111 I = FIRST, LAST, STEP
11111     WRITE(*,10000) I
      CONTINUE

10000 FORMAT(I5)
99999 FORMAT('ERROR')
99998 STOP
      END
takk@deb9:~$ gfortran seq.f
takk@deb9:~$ ./a.out
ERROR
takk@deb9:~$ ./a.out 1 5 10 20
ERROR
takk@deb9:~$ ./a.out 0 2 10
    0
    2
    4
    6
    8
   10
takk@deb9:~$ ./a.out 3 5
    3
    4
    5
takk@deb9:~$ ./a.out 4
    1
    2
    3
    4
takk@deb9:~$

一応動いたようです。

Fortran nlコマンドを作る

まだFORTRAN続いています。前回作ったプログラムをベースに、各行に番号をつけるnlコマンドっぽいものを作ってみます。

行番号を格納する変数はNにします。Nだとデフォルトで整数型ではないので、宣言しておきます。

takk@deb9:~$ cat nl1.f
      PROGRAM nl1
      INTEGER N
      CHARACTER*100 LINE
      N = 1
      DO
        READ(*,*,end=99999) LINE
        WRITE(*,*) N,LINE(1:INDEX(LINE,' '))
        N = N + 1
      END DO
99999 STOP
      END
takk@deb9:~$ gfortran nl1.f
takk@deb9:~$ seq 10 | ./a.out
           1 1
           2 2
           3 3
           4 4
           5 5
           6 6
           7 7
           8 8
           9 9
          10 10
takk@deb9:~$

nlコマンドの方を確認してみます。

takk@deb9:~$ seq 10 | nl
     1  1
     2  2
     3  3
     4  4
     5  5
     6  6
     7  7
     8  8
     9  9
    10  10
takk@deb9:~$

数字の部分6桁のようですが、セパレータは空白だったかタブだったかよく分からないので、odでダンプしてみます。

takk@deb9:~$ seq 10 | nl | od -tx1 -w9
0000000 20 20 20 20 20 31 09 31 0a
0000011 20 20 20 20 20 32 09 32 0a
0000022 20 20 20 20 20 33 09 33 0a
0000033 20 20 20 20 20 34 09 34 0a
0000044 20 20 20 20 20 35 09 35 0a
0000055 20 20 20 20 20 36 09 36 0a
0000066 20 20 20 20 20 37 09 37 0a
0000077 20 20 20 20 20 38 09 38 0a
0000110 20 20 20 20 20 39 09 39 0a
0000121 20 20 20 20 31 30 09 31 30
0000132 0a
0000133
takk@deb9:~$

09なのでタブですね。
FORTRANのプログラムの方も、6桁表示でタブ区切りに改造しましょう。

takk@deb9:~$ cat nl2.f
      PROGRAM nl1
      INTEGER N
      CHARACTER*100 LINE
      N = 1
      DO
        READ(*,*,end=99999) LINE
        WRITE(*,11111) N,LINE(1:INDEX(LINE,' '))
        N = N + 1
      END DO
11111 FORMAT(I6,'ここはタブです',A)
99999 STOP
      END
takk@deb9:~$

実行してみます。

takk@deb9:~$ gfortran nl2.f
takk@deb9:~$ seq 10 | ./a.out
     1  1
     2  2
     3  3
     4  4
     5  5
     6  6
     7  7
     8  8
     9  9
    10  10
takk@deb9:~$

odでも確認します。

takk@deb9:~$ !! | od -tx1 -w9
seq 10 | ./a.out | od -tx1 -w9
0000000 20 20 20 20 20 31 09 31 20
0000011 0a 20 20 20 20 20 32 09 32
0000022 20 0a 20 20 20 20 20 33 09
0000033 33 20 0a 20 20 20 20 20 34
0000044 09 34 20 0a 20 20 20 20 20
0000055 35 09 35 20 0a 20 20 20 20
0000066 20 36 09 36 20 0a 20 20 20
0000077 20 20 37 09 37 20 0a 20 20
0000110 20 20 20 38 09 38 20 0a 20
0000121 20 20 20 20 39 09 39 20 0a
0000132 20 20 20 20 31 30 09 31 30
0000143 20 0a
0000145
takk@deb9:~$

ずれてます。改行0aの前に空白20が入っているようです。

部分文字列の取得位置がずれてました。-1します。

        WRITE(*,11111) N,LINE(1:INDEX(LINE,' ')-1)

変更したプログラムを実行したらぴったり合いました。

takk@deb9:~$ seq 10 | ./a.out | od -tx1 -w9
0000000 20 20 20 20 20 31 09 31 0a
0000011 20 20 20 20 20 32 09 32 0a
0000022 20 20 20 20 20 33 09 33 0a
0000033 20 20 20 20 20 34 09 34 0a
0000044 20 20 20 20 20 35 09 35 0a
0000055 20 20 20 20 20 36 09 36 0a
0000066 20 20 20 20 20 37 09 37 0a
0000077 20 20 20 20 20 38 09 38 0a
0000110 20 20 20 20 20 39 09 39 0a
0000121 20 20 20 20 31 30 09 31 30
0000132 0a
0000133
takk@deb9:~$

Fortran echoコマンドを作る

echo 1

FORTRANです。echoコマンド作ります。いつものようにオプション無です。

echoコマンドは、パラメータで渡した文字列を表示するコマンドなので、パラメータ数分の文字列表示を繰り返せばできあがります。

takk@deb9:~$ cat echo1.f
      PROGRAM echo
      CHARACTER*100 ARGS
      INTEGER N
      N = IARGC()
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        WRITE(*,*) ARGS(1:INDEX(ARGS,' ')-1)
11111 CONTINUE
      STOP
      END
takk@deb9:~$

IARGC()でパラメータ数を取得して、DO文で回すだけです。
実行してみます。

takk@deb9:~$ gfortran echo1.f
takk@deb9:~$ ./a.out aaa bbb ccc
 aaa
 bbb
 ccc
takk@deb9:~$

だめですね。echoコマンドと表示が異なります。
本物のechoはこのような表示です。

takk@deb9:~$ echo aaa bbb ccc
aaa bbb ccc
takk@deb9:~$

FORMATを指定して表示するように変更してみましょう。

takk@deb9:~$ cat echo2.f
      PROGRAM echo
      CHARACTER*100 ARGS
      INTEGER N
      N = IARGC()
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        WRITE(*,22222) ARGS(1:INDEX(ARGS,' ')-1)
11111 CONTINUE
22222 FORMAT(A)
      STOP
      END
takk@deb9:~$

しかしこれでも、改行は取り除くことができないようです。

takk@deb9:~$ gfortran echo2.f
takk@deb9:~$ ./a.out aaa bbb ccc
aaa
bbb
ccc
takk@deb9:~$

いろいろ調べてみると、FORMAT指定に$を書けば、改行なし扱いとなることが分かりました。

takk@deb9:~$ cat echo3.f
      PROGRAM echo
      CHARACTER*100 ARGS
      INTEGER N
      N = IARGC()
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        WRITE(*,22222) ARGS(1:INDEX(ARGS,' ')-1)
11111 CONTINUE
22222 FORMAT(A$)
      STOP
      END
takk@deb9:~$

しかしビルドするとこんなエラーが。

takk@deb9:~$ gfortran echo3.f
echo3.f:9:14:

 22222 FORMAT(A$)
              1
Fatal Error: Invalid character ‘$’ at (1). Use ‘-fdollar-ok’ to allow it as an extension
compilation terminated.
takk@deb9:~$

オプションを使えと言われてるので、仕方なく-fdollar-okオプションを使うことにします。

takk@deb9:~$ gfortran echo3.f -fdollar-ok
takk@deb9:~$ ./a.out aaa bbb ccc
aaabbbccctakk@deb9:~$

惜しいです。後はパラメータ毎に空白を表示するのと、最後だけ改行を表示すればよいですね。
続く。

echo 2

最後のパラメータの場合、改行するFORMATを指定するように改造しました。

takk@deb9:~$ cat echo4.f
      PROGRAM echo
      CHARACTER*100 ARGS
      INTEGER N
      N = IARGC()
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        IF(I.EQ.N) THEN
          WRITE(*,33333) ARGS(1:INDEX(ARGS,' ')-1)
        ELSE
          WRITE(*,22222) ARGS(1:INDEX(ARGS,' '))
        END IF
11111 CONTINUE
22222 FORMAT(A$)
33333 FORMAT(A)
      STOP
      END
takk@deb9:~$

実行してみます。

takk@deb9:~$ gfortran echo4.f -fdollar-ok
takk@deb9:~$ ./a.out aaa bbb ccc
aaa bbb ccc
takk@deb9:~$

echoコマンドらしくなりました。次はオプションも指定できるように改造しましょう。
簡単なところから攻めていきたいので-nオプションぐらいにしておきます。echoで表示する文字列の改行をしないオプションです。パラメータに’-n’がある場合のみ有効にします。ちょうど最後のパラメータかどうかで、改行するしないを決めているので、分岐の条件を少しだけいじればいけそうです。

と思ったものの、FORTRANで文字列の比較ってどうやればよいのでしょう。IFで判定してみます。

takk@deb9:~$ cat strcmp.f
      PROGRAM echo
      CHARACTER*100 A,B,C
      A='ABCDE'
      B='ABCDE'
      C='ABCDEF'
      IF((A.EQ.B)) THEN
          WRITE(*,*) 'TRUE'
        ELSE
          WRITE(*,*) 'FALSE'
      END IF
      IF((A.EQ.C)) THEN
          WRITE(*,*) 'TRUE'
        ELSE
          WRITE(*,*) 'FALSE'
      END IF
      STOP
      END
takk@deb9:~$ 

実行すると、

takk@deb9:~$ gfortran strcmp.f
takk@deb9:~$ ./a.out
 TRUE
 FALSE
takk@deb9:~$

AとBは一致しますので、TRUEと表示され、AとCは不一致なので、FALSEと表示されました。古い言語なのに、感で動くってすごいです。
ということで、-nオプションの判定も簡単そうなので、作ってみます。文字列の中から-nを見つけたら、改行しないフラグをONにするようにします。
このようなプログラムになりました。

takk@deb9:~$ cat echo5.f
      PROGRAM echo
      CHARACTER*100 ARGS
      INTEGER N,NFLAG
      NFLAG=0
      N = IARGC()
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        IF((ARGS.EQ.'-n')) THEN
          NFLAG = 1
        END IF
        IF((I.EQ.N) .AND. (NFLAG.EQ.0) ) THEN
          WRITE(*,33333) ARGS(1:INDEX(ARGS,' ')-1)
        ELSE
          WRITE(*,22222) ARGS(1:INDEX(ARGS,' '))
        END IF
11111 CONTINUE
22222 FORMAT(A$)
33333 FORMAT(A)
      STOP
      END
takk@deb9:~$

実行します。

takk@deb9:~$ gfortran echo5.f -fdollar-ok
takk@deb9:~$ ./a.out aaa bbb ccc
aaa bbb ccc
takk@deb9:~$ ./a.out -n aaa bbb ccc
-n aaa bbb ccc takk@deb9:~$

動いたように見えましたが、表示される文字列に-nが紛れ込んでしまいました。
続く。

echo 3

FORTRANでechoコマンドの続きです。
前回-nオプションに対応しましたが動きはいまいちでした。
本物のechoコマンド方はどんな動きをするのでしょう。

takk@deb9:~$ echo HELLO -n
HELLO -n
takk@deb9:~$ echo -n HELLO
HELLOtakk@deb9:~$

どうやら、オプションは先頭のパラメータでないといけないようですね。
先頭のパラメータだけオプションとしてみなすように修正するのは簡単ですね。

      IF((ARGS.EQ.'-n') .AND. (I.EQ.1)) THEN

このようにオプションの判定を最初のパラメータの場合のみに修正しました。
そしてできあがったプログラムがこれです。

takk@deb9:~$ cat echo6.f
      PROGRAM echo
      CHARACTER*100 ARGS
      INTEGER N,NFLAG
      NFLAG=0
      N = IARGC()
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        IF((ARGS.EQ.'-n') .AND. (I.EQ.1)) THEN
          NFLAG = 1
        END IF
        IF((I.EQ.N) .AND. (NFLAG.EQ.0) ) THEN
          WRITE(*,33333) ARGS(1:INDEX(ARGS,' ')-1)
        ELSE
          WRITE(*,22222) ARGS(1:INDEX(ARGS,' '))
        END IF
11111 CONTINUE
22222 FORMAT(A$)
33333 FORMAT(A)
      STOP
      END
takk@deb9:~$

実行してみます。

takk@deb9:~$ gfortran echo6.f -fdollar-ok
takk@deb9:~$ ./a.out -n aaa bbb ccc
-n aaa bbb ccc takk@deb9:~$

忘れてました。先頭パラメータがオプションの場合は、表示する文字列から除外しなければなりません。
オプションを見つけたらIに1プラスしてみます。

        IF((ARGS.EQ.'-n') .AND. (I.EQ.1)) THEN
          NFLAG = 1
          I = I + 1
        END IF
takk@deb9:~$ gfortran echo7.f -fdollar-ok
echo7.f:10:72:

echo7.f:6:72:

       DO 11111 I=1,N
                                                                        2
echo7.f:10:72:

           I = I + 1
                                                                        1
Error: Variable ‘i’ at (1) cannot be redefined inside loop beginning at (2)
takk@deb9:~$

DOで回すループ変数の値を途中で変更するのはまずいようです。ビルドエラーになります。
そもそももっと簡単な方法がありました。ELSE IFを使えばよいです。

takk@deb9:~$ cat echo8.f
      PROGRAM echo
      CHARACTER*100 ARGS
      INTEGER N,NFLAG
      NFLAG=0
      N = IARGC()
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        IF((ARGS.EQ.'-n') .AND. (I.EQ.1)) THEN
          NFLAG = 1
        ELSE IF((I.EQ.N) .AND. (NFLAG.EQ.0) ) THEN
          WRITE(*,33333) ARGS(1:INDEX(ARGS,' ')-1)
        ELSE
          WRITE(*,22222) ARGS(1:INDEX(ARGS,' '))
        END IF
11111 CONTINUE
22222 FORMAT(A$)
33333 FORMAT(A)
      STOP
      END
takk@deb9:~$

実行します。

takk@deb9:~$ ./a.out -n aaa bbb ccc
aaa bbb ccc takk@deb9:~$
takk@deb9:~$ ./a.out aaa bbb ccc -n
aaa bbb ccc -n
takk@deb9:~$ ./a.out aaa bbb ccc
aaa bbb ccc
takk@deb9:~$ ./a.out -n
takk@deb9:~$

とてもうまくいきました。

Fortran catコマンドを作る

cat 1

まだまだ続くFORTRANです。
echoコマンドでパラメータで指定した複数の文字列を表示することができましたので、catコマンドも作れそうな気がします。今回はcatコマンドです。
まずcatコマンドのパラメータに指定するファイルは以下のコマンドで生成しておきます。

takk@deb9:~$ seq 10 10 300 | split -dl10
takk@deb9:~$ ls
cat0.f  x00  x01  x02
takk@deb9:~$

xから始まるファイルにそれぞれ10~300までの数字が10毎に格納されています。

takk@deb9:~$ head x*
==> x00 <==
10
20
30
40
50
60
70
80
90
100

==> x01 <==
110
120
130
140
150
160
170
180
190
200

==> x02 <==
210
220
230
240
250
260
270
280
290
300
takk@deb9:~$

catコマンドは連結されて表示されますので、x00~x02をパラメータに指定するとこのような表示になります。

takk@deb9:~$ cat x00 x01 x02
10
20
30
40
50
60
70
80
90
100
110
120
130
140
150
160
170
180
190
200
210
220
230
240
250
260
270
280
290
300
takk@deb9:~$
takk@deb9:~$ cat -n x00 x01 x02
     1  10
     2  20
     3  30

     (省略)

    28  280
    29  290
    30  300
takk@deb9:~$

指定したファイル名を表示するところから始めます。

takk@deb9:~$ cat cat1.f
      PROGRAM cat
      CHARACTER*100 ARGS
      INTEGER N
      N = IARGC()
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
          WRITE(*,*) ARGS(1:INDEX(ARGS,' ')-1)
11111 CONTINUE
      STOP
      END

takk@deb9:~$

実行するとこのようになります。

takk@deb9:~$ gfortran cat1.f
takk@deb9:~$ ./a.out x00 x01 x02
 x00
 x01
 x02
takk@deb9:~$

次は一行目だけ読み出して表示するプログラムに修正します。

takk@deb9:~/gf$ cat cat2.f
      PROGRAM cat
      CHARACTER*100 ARGS,LINE
      INTEGER N
      N = IARGC()
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        OPEN(UNIT=10,FILE=ARGS(1:INDEX(ARGS,' ')-1),
     1  STATUS='OLD',
     2  ACCESS='SEQUENTIAL')
        READ(10,*) LINE
        WRITE(*,*) LINE
        CLOSE(10)
11111 CONTINUE
      STOP
      END

takk@deb9:~$

実行結果です。

takk@deb9:~$ gfortran cat2.f
takk@deb9:~$ ./a.out x00 x01 x02
 10                                                                             
 110                                                                            
 210                                                                            
takk@deb9:~$

続きます。

cat 2

ファイル先頭の一行のみの表示プログラムをベースに、ファイルを読み込んですべて表示するプログラムに修正します。

takk@deb9:~$ cat cat3.f
      PROGRAM cat
      CHARACTER*100 ARGS,LINE
      INTEGER N
      N = IARGC()
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        OPEN(UNIT=10,FILE=ARGS(1:INDEX(ARGS,' ')-1),
     1  STATUS='OLD',
     2  ACCESS='SEQUENTIAL')
   10   READ(10,*,END=100) LINE
        WRITE(*,*) LINE
        GOTO 10
  100 CLOSE(10,STATUS='KEEP')
11111 CONTINUE
      STOP
      END

takk@deb9:~$
takk@deb9:~$ gfortran cat3.f
takk@deb9:~$ ./a.out x00 x01 x02
 10                                                                             

 20                                                                             

 30                                                                             

(省略)

 270                                                                            

 280                                                                            

 290                                                                            

 300                                                                            
takk@deb9:~$

忘れてました。末尾の空白をトリムする必要があります。
しかし、本当の空白の場合は、トリムしてはいけません。どうしましょう。
今のスキルでは難しいので成長してから作ることにします。

続く

cat 3

FORTRANで作ったcatコマンドに-nオプションをつけます。

takk@deb9:~$ cat cat3.f
      PROGRAM cat
      CHARACTER*100 ARGS,LINE
      INTEGER N
      N = IARGC()
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        OPEN(UNIT=10,FILE=ARGS(1:INDEX(ARGS,' ')-1),
     1  STATUS='OLD',
     2  ACCESS='SEQUENTIAL')
   10   READ(10,*,END=100) LINE
        WRITE(*,*) LINE
        GOTO 10
  100 CLOSE(10,STATUS='KEEP')
11111 CONTINUE
      STOP
      END

takk@deb9:~$

前回のこのプログラムのバッファサイズを100から50ぐらいにして、ターミナルで改行されず表示できるように修正しておきます。

      CHARACTER*50 ARGS,LINE

行番号を表示するために必要な変数は、行番号そのものを格納する変数と、
オプションの状態を格納するフラグ、ぐらいでしょうか。
それぞれNUMとNFLAGと名付けました。

      INTEGER N,NFLAG,NUM

-nが指定されたかどうかの判定は、先頭パラメータが’-n’の場合としましょう。

      IF((I.EQ.1).AND.(ARGS.EQ.'-n')) 

あとは行番号表示ですが、WRITEの引数を増やすだけです。

      WRITE(*,*) NUM,LINE

修正したプログラムです。

takk@deb9:~$ cat cat4.f
      PROGRAM cat
      CHARACTER*50 ARGS,LINE
      INTEGER N,NFLAG,NUM
      NFLAG=0
      N = IARGC()
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        IF((I.EQ.1).AND.(ARGS.EQ.'-n')) THEN
          NFLAG=1
        ELSE
          NUM=1
          OPEN(UNIT=10,FILE=ARGS(1:INDEX(ARGS,' ')-1),
     1    STATUS='OLD',
     2    ACCESS='SEQUENTIAL')
   10     READ(10,*,END=100) LINE
          NUM=NUM+1
          IF(NFLAG.EQ.1) THEN
            WRITE(*,*) NUM,LINE
          ELSE
            WRITE(*,*) LINE
          END IF
          GOTO 10
  100     CLOSE(10,STATUS='KEEP')
      EN  D IF
11111 CONTINUE
      STOP
      END

takk@deb9:~$

実行してみます。

takk@deb9:~$ ./a.out -n x00
           2 10
           3 20
           4 30
           5 40
           6 50
           7 60
           8 70
           9 80
          10 90
          11 100
takk@deb9:~$

おっと。カウントの位置を間違えました。WRITEの後じゃないといけませんね。

takk@deb9:~$ gfortran cat4.f
takk@deb9:~$ ./a.out -n x00
           1 10
           2 20
           3 30
           4 40
           5 50
           6 60
           7 70
           8 80
           9 90
          10 100
takk@deb9:~$

本物のcatと桁数を合わせたいので、cat -nを確認します。

takk@deb9:~$ cat -n x00
     1  10
     2  20
     3  30
     4  40
     5  50
     6  60
     7  70
     8  80
     9  90
    10  100
takk@deb9:~$

行番号は6桁ですね。区切り文字も空白なのかタブなのか確認しましょう。

takk@deb9:~$ cat -n x00 | od -tx1 -An -w10
 20 20 20 20 20 31 09 31 30 0a
 20 20 20 20 20 32 09 32 30 0a
 20 20 20 20 20 33 09 33 30 0a
 20 20 20 20 20 34 09 34 30 0a
 20 20 20 20 20 35 09 35 30 0a
 20 20 20 20 20 36 09 36 30 0a
 20 20 20 20 20 37 09 37 30 0a
 20 20 20 20 20 38 09 38 30 0a
 20 20 20 20 20 39 09 39 30 0a
 20 20 20 20 31 30 09 31 30 30
 0a
takk@deb9:~$

タブですね。では、cat -nの書式に合わせて、FORTRANのプログラムも改造してみます。
修正後のプログラムです。

takk@deb9:~$ cat cat4.f
      PROGRAM cat
      CHARACTER*50 ARGS,LINE
      INTEGER N,NFLAG,NUM
      NFLAG=0
      N = IARGC()
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        IF((I.EQ.1).AND.(ARGS.EQ.'-n')) THEN
          NFLAG=1
        ELSE
          NUM=1
          OPEN(UNIT=10,FILE=ARGS(1:INDEX(ARGS,' ')-1),
     1    STATUS='OLD',
     2    ACCESS='SEQUENTIAL')
   10     READ(10,*,END=100) LINE
          IF(NFLAG.EQ.1) THEN
            WRITE(*,22222) NUM,LINE
          ELSE
            WRITE(*,33333) LINE
          END IF
          NUM=NUM+1
          GOTO 10
  100     CLOSE(10,STATUS='KEEP')
        END IF
11111 CONTINUE
22222 FORMAT(I6,'       ',A)
33333 FORMAT(A)
      STOP
      END

takk@deb9:~$

実行結果です。

takk@deb9:~$ gfortran cat4.f
takk@deb9:~$ ./a.out
takk@deb9:~$ ./a.out x00
10
20
30
40
50
60
70
80
90
100
takk@deb9:~$ ./a.out -n x00
     1  10
     2  20
     3  30
     4  40
     5  50
     6  60
     7  70
     8  80
     9  90
    10  100
takk@deb9:~$

Fortran tacコマンドを作る

tac 1

今回はFORTRANでtacコマンドを作ります。まずtacの動きを確認します。

takk@deb9:~$ cat x00
10
20
30
40
50
60
70
80
90
100
takk@deb9:~$ tac x00
100
90
80
70
60
50
40
30
20
10
takk@deb9:~$

上下逆にするだけですね。簡単ですね。いまどきのスクリプトならこんな感じで3行で書ける処理です。

takk@deb9:~$ cat tac.pl
@lines = <>;
@reverse_lines = reverse @lines;
print"@reverse_lines";
takk@deb9:~$
takk@deb9:~$ perl tac.pl x00
100
 90
 80
 70
 60
 50
 40
 30
 20
 10
takk@deb9:~$

FORTRAN77だとそう簡単にいかなさそうです。まあ学び始めたばかりなので。

まずは文字列を配列に格納する処理を作ります。

takk@deb9:~$ cat tac1.f
      PROGRAM cat
      CHARACTER*50 ARGS,LINE,LINES(100)
      INTEGER N,NUM
      NFLAG=0
      N = IARGC()
      NUM=1
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        OPEN(UNIT=10,FILE=ARGS(1:INDEX(ARGS,' ')-1),
     1  STATUS='OLD',
     2  ACCESS='SEQUENTIAL')
   10   READ(10,*,END=100) LINE
        LINES(NUM) = LINE
        NUM=NUM+1
        GOTO 10
  100   CLOSE(10,STATUS='KEEP')
11111 CONTINUE
      STOP
      END

takk@deb9:~$

実行はしてみますが、何の表示もしていないので、エラーが起きなかったことが分かるだけです。

takk@deb9:~$ gfortran tac1.f
takk@deb9:~$ ./a.out x00
takk@deb9:~$

次は格納した配列の内容を表示するプログラムに改造します。

takk@deb9:~$ cat tac2.f
      PROGRAM cat
      CHARACTER*50 ARGS,LINE,LINES(100)
      INTEGER N,NUM
      NFLAG=0
      N = IARGC()
      NUM=1
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        OPEN(UNIT=10,FILE=ARGS(1:INDEX(ARGS,' ')-1),
     1  STATUS='OLD',
     2  ACCESS='SEQUENTIAL')
   10   READ(10,*,END=100) LINE
        LINES(NUM) = LINE
        NUM=NUM+1
        GOTO 10
  100   CLOSE(10,STATUS='KEEP')
11111 CONTINUE
      DO 22222 I=1,NUM-1
        WRITE(*,33333) LINES(I)
22222 CONTINUE

33333 FORMAT(A)
      STOP
      END

takk@deb9:~$

結果です。

takk@deb9:~$ gfortran tac2.f
takk@deb9:~$ ./a.out x00
10
20
30
40
50
60
70
80
90
100
takk@deb9:~$

最後に表示する配列の要素が逆になるような添え字への計算を加えます。

      DO 22222 I=1,NUM-1
        WRITE(*,33333) LINES(NUM-I)
22222 CONTINUE

このように修正しました。
結果です。

takk@deb9:~$ gfortran tac3.f
takk@deb9:~$ ./a.out x00
100
90
80
70
60
50
40
30
20
10
takk@deb9:~$

tac 2

複数ファイル指定時の表示を確認してみます。

takk@deb9:~$ ./a.out x00 x01
200
190
180
170
160
150
140
130
120
110
100
90
80
70
60
50
40
30
20
10
takk@deb9:~$

複数指定したときもちゃんと逆さまになってますので、合ってそうです。

では本物のtacを確認します。

takk@deb9:~$ tac x00 x01
100
90
80
70
60
50
40
30
20
10
200
190
180
170
160
150
140
130
120
110
takk@deb9:~$

なんと。想像と違ってました。パラメータで指定したファイルを読み込み順番からして逆さまなんですね。

FORTRANのプログラムの方も、パラメータの読み込み順序を逆にしてみます。

      CALL GETARG(I,ARGS)

を、

      CALL GETARG(N-I+1,ARGS)

とするだけですね。プログラムはこうなりました。

takk@deb9:~$ cat tac4.f
      PROGRAM cat
      CHARACTER*50 ARGS,LINE,LINES(100)
      INTEGER N,NUM
      NFLAG=0
      N = IARGC()
      NUM=1
      DO 11111 I=1,N
        CALL GETARG(N-I+1,ARGS)
        OPEN(UNIT=10,FILE=ARGS(1:INDEX(ARGS,' ')-1),
     1  STATUS='OLD',
     2  ACCESS='SEQUENTIAL')
   10   READ(10,*,END=100) LINE
        LINES(NUM) = LINE
        NUM=NUM+1
        GOTO 10
  100   CLOSE(10,STATUS='KEEP')
11111 CONTINUE
      DO 22222 I=1,NUM-1
        WRITE(*,33333) LINES(NUM-I)
22222 CONTINUE

33333 FORMAT(A)
      STOP
      END

takk@deb9:~$

結果です。

takk@deb9:~$ gfortran tac4.f
takk@deb9:~$ ./a.out x00 x01
100
90
80
70
60
50
40
30
20
10
200
190
180
170
160
150
140
130
120
110
takk@deb9:~$

FORTRANに慣れてきたのでしょうか。プログラムの改造が早くなってきた気がします。

Fortran revコマンドを作る

FORTRANです。
今回はまったく作れる気がしないrevコマンドを作ろうかと思います。

revコマンドは、テキストを左右逆にするコマンドです。使い方はこんな感じです。

takk@deb9:~$ seq 12345 12349 > x00
takk@deb9:~$ cat x00
12345
12346
12347
12348
12349
takk@deb9:~$ rev x00
54321
64321
74321
84321
94321
takk@deb9:~$

ではFORTRANのプログラムを作っていきます。いきなり左右逆は難しいので、ただそのまま表示するだけのサブルーチンREVを作ります。`

takk@deb9:~$ cat rev1.f
      PROGRAM cat
      CHARACTER*100 ARGS,LINE
      INTEGER N
      N = IARGC()
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        OPEN(UNIT=10,FILE=ARGS(1:INDEX(ARGS,' ')-1),
     1  STATUS='OLD',
     2  ACCESS='SEQUENTIAL')
   10   READ(10,*,END=100) LINE
        CALL REV(LINE)
        GOTO 10
  100 CLOSE(10,STATUS='KEEP')
11111 CONTINUE
      STOP
      END

      SUBROUTINE REV(LINE)
        CHARACTER*50 LINE
        WRITE(*,33333) LINE
33333   FORMAT(A)
        RETURN
      END

takk@deb9:~$

実行すると、

takk@deb9:~$ ./a.out x00
12345
12346
12347
12348
12349
takk@deb9:~$

期待通り表示されました。

では文字列を左右逆に表示していきます。
文字列変数は、変数名(先頭:最後) で、部分抽出ができるのでそれを使うことにします。

      SUBROUTINE REV(LINE)
        CHARACTER*50 LINE
        WRITE(*,33333) LINE(5:5)
33333   FORMAT(A)
        RETURN
      END
takk@deb9:~$ ./a.out x00
5
6
7
8
9
takk@deb9:~$

普通に配列として1文字アクセスできないかなと思い、このようなプログラムも作ってみましたが、こちらはビルドエラーになりました。

      SUBROUTINE REV(LINE)
        CHARACTER*50 LINE
        WRITE(*,33333) LINE(5)
33333   FORMAT(A)
        RETURN
      END

1文字だけ抽出することで、文字列の最後の位置から逆順に表示していけばrevコマンドのできあがりです。

takk@deb9:~$ cat rev2.f
      PROGRAM cat
      CHARACTER*100 ARGS,LINE
      INTEGER N
      N = IARGC()
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        OPEN(UNIT=10,FILE=ARGS(1:INDEX(ARGS,' ')-1),
     1  STATUS='OLD',
     2  ACCESS='SEQUENTIAL')
   10   READ(10,*,END=100) LINE
        CALL REV(LINE)
        GOTO 10
  100 CLOSE(10,STATUS='KEEP')
11111 CONTINUE
      STOP
      END

      SUBROUTINE REV(LINE)
        CHARACTER*100 LINE
        INTEGER SP_FLAG,LOC
        SP_FLAG=0
        DO 2222 I=1,10
          LOC=10-I+1
          IF(SP_FLAG.EQ.0) THEN
            IF(LINE(LOC:LOC).NE. ' ') THEN
              SP_FLAG=1
              GOTO 1111
            END IF
            GOTO 2222
          END IF
 1111    WRITE(*,33333) LINE(LOC:LOC)
 2222   CONTINUE
        WRITE(*,*) ''
        RETURN
33333   FORMAT(A$)
      END

takk@deb9:~$

結果です。

takk@deb9:~$ ./a.out x00
54321
64321
74321
84321
94321
takk@deb9:~$

Fortran cutコマンドを作る

cut 1

FORTRAN、次はcutコマンド作ります。
まずcutをかけるファイルをseqコマンドで作っておきます。

takk@deb9:~$ seq 10010 100 13000 | split -dl10
takk@deb9:~$ ls
x00  x01  x02
takk@deb9:~$ cat x00
10010
10110
10210
10310
10410
10510
10610
10710
10810
10910
takk@deb9:~$ cat x02
12010
12110
12210
12310
12410
12510
12610
12710
12810
12910
takk@deb9:~$

生成したファイルx00にcutコマンドを使ってみます。

takk@deb9:~$ cut -b1-3 x00
100
101
102
103
104
105
106
107
108
109
takk@deb9:~$

1Byte目から3Byte目の文字列を抽出しました。

ではFORTRANで作ってみようと思いますが、いきなりオプション解析するのは難しそうなので、固定で2Byte目から4Byte目を抽出するだけのプログラムから始めます。
このようになりました。

takk@deb9:~$ cat cut1.f
      CHARACTER*100 ARGS,LINE
      INTEGER N
      N = IARGC()
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        OPEN(UNIT=10,FILE=ARGS(1:INDEX(ARGS,' ')-1),
     1  STATUS='OLD',
     2  ACCESS='SEQUENTIAL')
   10   READ(10,*,END=100) LINE
        CALL CUT(LINE)
        GOTO 10
  100 CLOSE(10,STATUS='KEEP')
11111 CONTINUE
      STOP
      END

      SUBROUTINE CUT(LINE)
        CHARACTER*100 LINE
 1111   WRITE(*,33333) LINE(2:4)
        RETURN
33333   FORMAT(A)
      END

takk@deb9:~$

実行してみます。

takk@deb9:~$ gfortran cut1.f
takk@deb9:~$ ./a.out x00
001
011
021
031
041
051
061
071
081
091
takk@deb9:~$

元のファイルの内容を横に並べて確認します。2Byte~4Byteの位置の文字列と一致しますね。

takk@deb9:~$ !! | paste - x00
./a.out x00 | paste - x00
001     10010
011     10110
021     10210
031     10310
041     10410
051     10510
061     10610
071     10710
081     10810
091     10910
takk@deb9:~$

cut 2

オプションの読み込みを行います。
-bオプションは、カンマで区切ったり、ハイフンで区切ったりして指定できますが、まだそのレベルの字句解析ができる気がしないので、-bの後の1文字だけ取得するプログラムにします。

takk@deb9:~$ cat cut2.f
      CHARACTER*100 ARGS,LINE
      INTEGER N,B
      B = 1
      N = IARGC()
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        IF( (ARGS(1:2).EQ.'-b') .AND. (I.EQ.1)) THEN
          READ(ARGS(3:3),*) B
        ELSE
          OPEN(UNIT=10,FILE=ARGS(1:INDEX(ARGS,' ')-1),
     1    STATUS='OLD',
     2    ACCESS='SEQUENTIAL')
   10     READ(10,*,END=100) LINE
          CALL CUT(LINE,B)
          GOTO 10
  100     CLOSE(10,STATUS='KEEP')
        END IF
11111 CONTINUE
      STOP
      END

      SUBROUTINE CUT(LINE,B)
        INTEGER B
        CHARACTER*100 LINE
 1111   WRITE(*,33333) LINE(B:B)
        RETURN
33333   FORMAT(A)
      END

takk@deb9:~$
takk@deb9:~$ gfortran cut2.f
takk@deb9:~$ cat x00
10010
10110
10210
10310
10410
10510
10610
10710
10810
10910
takk@deb9:~$ ./a.out -b3 x00
0
1
2
3
4
5
6
7
8
9
takk@deb9:~$ ./a.out -b1 x00
1
1
1
1
1
1
1
1
1
1
takk@deb9:~$ ./a.out -b2 x00
0
0
0
0
0
0
0
0
0
0
takk@deb9:~$

-bオプションの指定数字が1桁なら範囲指定も簡単そうです。範囲指定もできる形に改造します。

takk@deb9:~$ cat cut3.f
      CHARACTER*100 ARGS,LINE
      INTEGER N,B1,B2
      B1 = 1
      B2 = 1
      N = IARGC()
      DO 11111 I=1,N
        CALL GETARG(I,ARGS)
        IF(ARGS(1:2).EQ.'-b') THEN
          IF(ARGS(4:4).EQ.'-') THEN
            READ(ARGS(3:3),*) B1
            READ(ARGS(5:5),*) B2
          ELSE
            READ(ARGS(3:3),*) B1
            B2 = B1
          END IF
        ELSE
          OPEN(UNIT=10,FILE=ARGS(1:INDEX(ARGS,' ')-1),
     1    STATUS='OLD',
     2    ACCESS='SEQUENTIAL')
   10     READ(10,*,END=100) LINE
          CALL CUT(LINE,B1,B2)
          GOTO 10
  100     CLOSE(10,STATUS='KEEP')
        END IF
11111 CONTINUE
      STOP
      END

      SUBROUTINE CUT(LINE,B1,B2)
        INTEGER B1,B2
        CHARACTER*100 LINE
 1111   WRITE(*,33333) LINE(B1:B2)
        RETURN
33333   FORMAT(A)
      END

takk@deb9:~$

結果です。

takk@deb9:~$ gfortran cut3.f
takk@deb9:~$ cut -b2-4 x00
001
011
021
031
041
051
061
071
081
091
takk@deb9:~$ ./a.out -b2-4 x00
001
011
021
031
041
051
061
071
081
091
takk@deb9:~$

cut 3

作ったSPLITを使って、cutコマンドの-bオプションを複数列指定できるようにします。
いきなりプログラムです。

takk@deb9:~$ cat cut5.f
      PROGRAM MAIN
        CHARACTER*100 ARGS,LINE
        CHARACTER*10 SSTRING(10)
        INTEGER N,SSIZE,COL(10)
        SSIZE = 1
        N = IARGC()
        DO 300 I=1,N
          CALL GETARG(I,ARGS)
          IF(ARGS(1:2).EQ.'-b') THEN
            CALL SPLIT(ARGS(3:),SSTRING,SSIZE)
            DO 100,J=1,SSIZE
              READ(SSTRING(J),*) COL(J)
  100       CONTINUE
          ELSE
            OPEN(UNIT=10,FILE=ARGS(1:INDEX(ARGS,' ')-1),
     1      STATUS='OLD',
     2      ACCESS='SEQUENTIAL')
   10       READ(10,*,END=200) LINE
            DO 150,J=1,SSIZE
              WRITE(*,1000) LINE(COL(J):COL(J))
  150       CONTINUE
              WRITE(*,*) ''
            GOTO 10
  200       CLOSE(10,STATUS='KEEP')
          END IF
  300   CONTINUE
 1000   FORMAT(A$)
        STOP
      END


      SUBROUTINE SPLIT(SRC,DEST,SPLIT_SIZE)
        CHARACTER*100 SRC
        CHARACTER*10 DEST(10)
        INTEGER IND,S1,S2,RSLT,SPLIT_SIZE
        IND = 0
        S2=-1
        I = 1
20100   RSLT = INDEX(SRC(IND+1:),',')
        IF(RSLT.NE.0) THEN
          S1 = IND+1
          IND = IND + RSLT
          S2 = IND-1
          DEST(I) = SRC(S1:S2)
          I = I + 1
          GOTO 20100
        END IF
        DEST(I) = SRC(S2+2:)
        SPLIT_SIZE = I
        RETURN
      END
takk@deb9:~$

できました。SPLIT関数がカンマが存在しない場合の挙動がおかしかったので、S2に初期値として-2を入れています。
結果です。

takk@deb9:~$ cat x00
10010
10110
10210
10310
10410
10510
10610
10710
10810
10910
takk@deb9:~$ gfortran cut5.f -fdollar-ok
takk@deb9:~$ ./a.out -b1,2,3,4 x00
1001
1011
1021
1031
1041
1051
1061
1071
1081
1091
takk@deb9:~$ ./a.out -b2,3,4 x00
001
011
021
031
041
051
061
071
081
091
takk@deb9:~$

少しずつですがcutコマンドが出来ていきますね。

cut 4

-bオプションの範囲指定もできるようにします。 範囲は-(ハイフン)で指定します。
最初にSPLIT関数の改造です。今はカンマ限定なので、ハイフンも指定できるように引数指定できるようにします。

      SUBROUTINE SPLIT(SRC,DEST,SPLIT_SIZE)

20100   RSLT = INDEX(SRC(IND+1:),',')

これをこのように変更。セパレータという意味でSEPという変数名にしました。

      SUBROUTINE SPLIT(SRC,SEP,DEST,SPLIT_SIZE)

        ~

        CHARACTER*1 SEP

        ~

20100   RSLT = INDEX(SRC(IND+1:),SEP)

テストプログラムを作って修正したSPLIT関数を動かします。
最初に,(カンマ)でスプリットして、スプリット後の各要素を-(ハイフン)でスプリットします。

takk@deb9:~$ cat split2.f
      PROGRAM MAIN
        CHARACTER*100 STRING
        CHARACTER*10 SSTRING1(10),SSTRING2(10)
        INTEGER I,SSIZE1,SSIZE2

        STRING='1-3,5,7-12,13,14'
        WRITE(*,*) STRING

        CALL SPLIT(STRING,',', SSTRING1,SSIZE1)
        WRITE(*,1100) SSIZE1

        DO 100 I=1,SSIZE1

          WRITE(*,1300) SSTRING1(I)

          CALL SPLIT(SSTRING1(I),'-', SSTRING2,SSIZE2)
          WRITE(*,1200) SSIZE2

  100   CONTINUE
 1100   FORMAT('SIZE1=',I3)
 1200   FORMAT('  SIZE2=',I3)
 1300   FORMAT('[',A,']')

        STOP
      END

      SUBROUTINE SPLIT(SRC,SEP,DEST,SPLIT_SIZE)
        CHARACTER*100 SRC
        CHARACTER*1 SEP
        CHARACTER*10 DEST(10)
        INTEGER IND,S1,S2,RSLT,SPLIT_SIZE
        IND = 0
        S2=-1
        I = 1
20100   RSLT = INDEX(SRC(IND+1:),SEP)
        IF(RSLT.NE.0) THEN
          S1 = IND+1
          IND = IND + RSLT
          S2 = IND-1
          DEST(I) = SRC(S1:S2)
          I = I + 1
          GOTO 20100
        END IF
        DEST(I) = SRC(S2+2:)
        SPLIT_SIZE = I
        RETURN
      END
takk@deb9:~$

実行します。

takk@deb9:~$ ./a.out
 1-3,5,7-12,13,14                                                                   
SIZE1=  5
[1-3       ]
  SIZE2=  3
[5         ]
  SIZE2=  2
[7-12      ]
  SIZE2=  2
[13        ]
  SIZE2=  1
[14        ]
  SIZE2=  1
takk@deb9:~$

ん~。何か間違ってます。1回目のSPLITの結果としてSIZE1に分割数を格納してますが、SIZE1=5なので合ってるのですが、2回目のSPLITの結果のSIZE2が間違ってます。
こつこつデバッグしていこうかと思います。(FORTRANもデバッグはGDBなんでしょうか)

cut 5

自作split関数もできたので、cutコマンド作成の続きをします。
-bオプションを解析して、抽出したい列番号を配列に格納する処理を作ります。

できたのがこれです。

takk@deb9:~$ cat -n cut5.f
     1        PROGRAM MAIN
     2          INTEGER DISP_LOC(100)
     3          CHARACTER*100 ARGS
     4          CALL GETARG(1,ARGS)
     5
     6          IF(ARGS(1:2).EQ.'-b') THEN
     7            CALL B_OPT(ARGS(3:),DISP_LOC)
     8          END IF
     9          STOP
    10        END
    11
    12        SUBROUTINE B_OPT(B_OPT_STRING,DISP_LOC)
    13          CHARACTER*100 B_OPT_STRING
    14          CHARACTER*100 SSTRING1(10),SSTRING2(10)
    15          INTEGER I,SSIZE1,SSIZE2,K,L,M
    16          INTEGER DISP_LOC(100)
    17          DO 10000 I=1,100
    18            DISP_LOC(I) = 0
    19  10000   CONTINUE
    20
    21          CALL SPLIT(B_OPT_STRING,',', SSTRING1,SSIZE1)
    22          DO 10200 I=1,SSIZE1
    23            CALL SPLIT(SSTRING1(I),'-', SSTRING2,SSIZE2)
    24            IF(SSIZE2.EQ.1) THEN
    25              READ(SSTRING1(I),*) L
    26              DISP_LOC(L) = 1
    27            ELSE
    28              READ(SSTRING2(1),*) L
    29              READ(SSTRING2(2),*) M
    30              DO 10100 K=L,M
    31                DISP_LOC(K) = 1
    32  10100       CONTINUE
    33            END IF
    34
    35  10200   CONTINUE
    36        END
    37

2行目のDISP_LOC配列は、-bで指定した配列番号に目印をつけるための変数です。
B_OPT関数内の17~19行目で初期化します。
24行目で、SSIZE2が1かどうか判定してますが、ハイフン入りかどうかをチェックし、ハイフン入りなら範囲と捉えて該当するDISP_LOCの配列に1を立てます。

ではかるくGDBで確認してみます。使うGDBスクリプトはこれです。

takk@deb9:~$ cat test5
break 9
set args -b1,2,3,4,5
run
p args
p DISP_LOC

set args -b1-3,5-8
run
p args
p DISP_LOC

set args -b3,5,7-9
run
p args
p DISP_LOC

set args -b2-10
run
p args
p DISP_LOC
takk@deb9:~$

スクリプトを指定してGDB起動します。

takk@deb9:~$ gdb a.out -x test5

~

Breakpoint 1, MAIN__ () at cut5.f:9
9               STOP
$1 = '-b1,2,3,4,5', ' ' <repeats 89 times>
$2 = (1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, ~

Breakpoint 1, MAIN__ () at cut5.f:9
9               STOP
$3 = '-b1-3,5-8', ' ' <repeats 91 times>
$4 = (1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, ~

Breakpoint 1, MAIN__ () at cut5.f:9
9               STOP
$5 = '-b3,5,7-9', ' ' <repeats 91 times>
$6 = (0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, ~

Breakpoint 1, MAIN__ () at cut5.f:9
9               STOP
$7 = '-b2-10', ' ' <repeats 94 times>
$8 = (0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, ~
(gdb)

うまく配列に値が設定されました。次回はファイルから読み込んでcut表示します。

cut 6

-bオプションに対応したcutコマンドを作成しました。

takk@deb9:~$ cat -n cut6.f
     1        PROGRAM MAIN
     2          CHARACTER*100 LINE
     3          INTEGER DISP_LOC(100),N
     4          CHARACTER*100 ARGS
     5
     6          N = IARGC()
     7          DO 300 I=1,N
     8            CALL GETARG(I,ARGS)
     9
    10            IF(ARGS(1:2).EQ.'-b') THEN
    11              CALL B_OPT(ARGS(3:),DISP_LOC)
    12            ELSE
    13              OPEN(UNIT=10,FILE=ARGS(1:INDEX(ARGS,' ')-1),
    14       1      STATUS='OLD',
    15       2      ACCESS='SEQUENTIAL')
    16     10       READ(10,*,END=100) LINE
    17              CALL CUT(LINE,DISP_LOC)
    18              GOTO 10
    19    100       CLOSE(10,STATUS='KEEP')
    20            END IF
    21    300 CONTINUE
    22            STOP
    23        END
    24
    25        SUBROUTINE B_OPT(B_OPT_STRING,DISP_LOC)
    26          CHARACTER*100 B_OPT_STRING
    27          CHARACTER*100 SSTRING1(10),SSTRING2(10)
    28          INTEGER I,SSIZE1,SSIZE2,K,L,M
    29          INTEGER DISP_LOC(100)
    30
    31          DO 10000 I=1,100
    32            DISP_LOC(I) = 0
    33  10000   CONTINUE
    34
    35          CALL SPLIT(B_OPT_STRING,',', SSTRING1,SSIZE1)
    36          DO 10200 I=1,SSIZE1
    37            CALL SPLIT(SSTRING1(I),'-', SSTRING2,SSIZE2)
    38            IF(SSIZE2.EQ.1) THEN
    39              READ(SSTRING1(I),*) L
    40              DISP_LOC(L) = 1
    41            ELSE
    42              READ(SSTRING2(1),*) L
    43              READ(SSTRING2(2),*) M
    44              DO 10100 K=L,M
    45                DISP_LOC(K) = 1
    46  10100       CONTINUE
    47            END IF
    48  10200   CONTINUE
    49        END
    50
    51        SUBROUTINE SPLIT(SRC,SEP,DEST,SPLIT_SIZE)
    52          CHARACTER*100 SRC
    53          CHARACTER*1 SEP
    54          CHARACTER*100 DEST(10)
    55          INTEGER IND,S1,S2,RSLT,SPLIT_SIZE
    56          IND = 0
    57          S2=-1
    58          I = 1
    59  20100   RSLT = INDEX(SRC(IND+1:),SEP)
    60          IF(RSLT.NE.0) THEN
    61            S1 = IND+1
    62            IND = IND + RSLT
    63            S2 = IND-1
    64            DEST(I) = SRC(S1:S2)
    65            I = I + 1
    66            GOTO 20100
    67          END IF
    68          DEST(I) = SRC(S2+2:)
    69          SPLIT_SIZE = I
    70          RETURN
    71        END
    72
    73        SUBROUTINE CUT(LINE,DISP_LOC)
    74          INTEGER DISP_LOC(100)
    75          CHARACTER*100 LINE
    76          DO 30100 I=1,100
    77            IF(DISP_LOC(I).EQ.1) THEN
    78              WRITE(*,31000) LINE(I:I)
    79            END IF
    80            IF(LINE(I:I).EQ.' ') THEN
    81              WRITE(*,*) ''
    82              GOTO 30200
    83            END IF
    84  30100   CONTINUE
    85  30200   RETURN
    86  31000   FORMAT(A$)
    87        END
takk@deb9:~$

73行目のCUT関数が、列抽出する関数です。100列分配列を確認して、1がはいっていたら、部分文字列の抽出をします。空白が出現したら、文字列の終端とみなして、改行するためにWRITE(*,*) ”を実行しています。

動くでしょうか。GDBを使ってみます。

takk@deb9:~$ gfortran -g -fdollar-ok cut6.f
takk@deb9:~$ gdb a.out

パラメータを設定し、パラメータ数の取得関数前まで実行します。

(gdb) set args -b2-3,5 x00
(gdb) l
1             PROGRAM MAIN
2               CHARACTER*100 LINE
3               INTEGER DISP_LOC(100),N
4               CHARACTER*100 ARGS
5
6               N = IARGC()
7               DO 300 I=1,N
8                 CALL GETARG(I,ARGS)
9
10                IF(ARGS(1:2).EQ.'-b') THEN
(gdb) b 6
Breakpoint 1 at 0x147b: file cut6.f, line 6.
(gdb) run
Starting program: /home/takk/a.out -b2-3,5 x00

Breakpoint 1, MAIN__ () at cut6.f:6
6               N = IARGC()
(gdb)

パラメータ数は2ですので、合ってます。

(gdb) n
7               DO 300 I=1,N
(gdb) p N
$1 = 2
(gdb)

1を設定する配列の位置も、-bで指定した通りとなっています。

(gdb) n
8                 CALL GETARG(I,ARGS)
(gdb)
10                IF(ARGS(1:2).EQ.'-b') THEN
(gdb)
11                  CALL B_OPT(ARGS(3:),DISP_LOC)
(gdb)
7               DO 300 I=1,N
(gdb) p DISP_LOC
$2 = (0, 1, 1, 0, 1, 0, 0, ~
(gdb)

ファイルから読み込んだ文字列もOKです。

(gdb) n
8                 CALL GETARG(I,ARGS)
(gdb)
10                IF(ARGS(1:2).EQ.'-b') THEN
(gdb)
15           2      ACCESS='SEQUENTIAL')
(gdb)
16         10       READ(10,*,END=100) LINE
(gdb)
17                  CALL CUT(LINE,DISP_LOC)
(gdb) p line
$3 = '10010', ' ' <repeats 95 times>
(gdb)
(gdb) n
000
18                  GOTO 10
(gdb)
16         10       READ(10,*,END=100) LINE
(gdb)
17                  CALL CUT(LINE,DISP_LOC)
(gdb) s
cut (line=..., disp_loc=..., _line=100) at cut6.f:76
76              DO 30100 I=1,100
(gdb) n
77                IF(DISP_LOC(I).EQ.1) THEN
(gdb)
80                IF(LINE(I:I).EQ.' ') THEN
(gdb)
76              DO 30100 I=1,100
(gdb)
77                IF(DISP_LOC(I).EQ.1) THEN
(gdb)
78                  WRITE(*,31000) LINE(I:I)
(gdb)
080               IF(LINE(I:I).EQ.' ') THEN
(gdb)

実行結果を見てしまった方が早そうです。

takk@deb9:~$ ./a.out -b1,2,3,4 x00
1001
1011
1021
1031
1041
1051
1061
1071
1081
1091
takk@deb9:~$ ./a.out -b1-4 x00
1001
1011
1021
1031
1041
1051
1061
1071
1081
1091
takk@deb9:~$ ./a.out -b2-3,5 x00
000
010
020
030
040
050
060
070
080
090
takk@deb9:~$

上手く行きました。

cut 7

リファクタリングします。
FORTRANの部分文字列抽出が活かせてなかったので、DISP_LOC配列の使い方を変えます。
二次元配列にして、2値で範囲を設定するようにします。範囲がない値の場合は、2値は同じ値になります。
このような関数になりました。DISP_LOCはOPT_INFOという名前に変更して、SSTRING1やSSTRING2も、COMMA_STRとHYPHEN_STRに変更しました。

 2000 SUBROUTINE B_OPT(B_OPT_STRING,COMMA_SIZE,OPT_INFO)
        CHARACTER*100 B_OPT_STRING,COMMA_STR(10),HYPHEN_STR(10)
        INTEGER OPT_INFO(100,2),COMMA_SIZE,HYPHEN_SIZE

        CALL SPLIT(B_OPT_STRING,',', COMMA_STR,COMMA_SIZE)
        DO 2100 I=1,COMMA_SIZE
          CALL SPLIT(COMMA_STR(I),'-', HYPHEN_STR,HYPHEN_SIZE)
          IF(HYPHEN_SIZE.EQ.1) THEN
            READ(COMMA_STR(I),*) L
            M = L
          ELSE
            READ(HYPHEN_STR(1),*) L
            READ(HYPHEN_STR(2),*) M
          END IF

          OPT_INFO(I,1) = L
          OPT_INFO(I,2) = M
 2100   CONTINUE
      END

これに伴って他関数もリファクタリングした全ソースがこれです。

akk@deb9:~$ cat -n cut7.f
     1   1000 PROGRAM MAIN
     2          CHARACTER*100 LINE,ARGS
     3          INTEGER OPT_INFO(100,2),OPT_SIZE
     4
     5          N = IARGC()
     6          DO 1300 I=1,N
     7            CALL GETARG(I,ARGS)
     8
     9            IF(ARGS(1:2).EQ.'-b') THEN
    10              CALL B_OPT(ARGS(3:),OPT_SIZE,OPT_INFO)
    11            ELSE
    12              OPEN(UNIT=10,FILE=ARGS(1:INDEX(ARGS,' ')-1),
    13       1      STATUS='OLD',
    14       2      ACCESS='SEQUENTIAL')
    15   1100       READ(10,*,END=1200) LINE
    16              CALL CUT(LINE,OPT_SIZE,OPT_INFO)
    17              GOTO 1100
    18   1200       CLOSE(10,STATUS='KEEP')
    19            END IF
    20   1300   CONTINUE
    21          STOP
    22        END
    23
    24   2000 SUBROUTINE B_OPT(B_OPT_STRING,COMMA_SIZE,OPT_INFO)
    25          CHARACTER*100 B_OPT_STRING,COMMA_STR(10),HYPHEN_STR(10)
    26          INTEGER OPT_INFO(100,2),COMMA_SIZE,HYPHEN_SIZE
    27
    28          CALL SPLIT(B_OPT_STRING,',', COMMA_STR,COMMA_SIZE)
    29          DO 2100 I=1,COMMA_SIZE
    30            CALL SPLIT(COMMA_STR(I),'-', HYPHEN_STR,HYPHEN_SIZE)
    31            IF(HYPHEN_SIZE.EQ.1) THEN
    32              READ(COMMA_STR(I),*) L
    33              M = L
    34            ELSE
    35              READ(HYPHEN_STR(1),*) L
    36              READ(HYPHEN_STR(2),*) M
    37            END IF
    38
    39            OPT_INFO(I,1) = L
    40            OPT_INFO(I,2) = M
    41   2100   CONTINUE
    42        END
    43
    44   3000 SUBROUTINE CUT(LINE,OPT_SIZE,OPT_INFO)
    45          INTEGER OPT_SIZE,OPT_INFO(100,2)
    46          CHARACTER*100 LINE
    47          DO 3100 I=1,OPT_SIZE
    48            J = OPT_INFO(I,1)
    49            K = OPT_INFO(I,2)
    50            WRITE(*,3300) LINE(J:K)
    51   3100   CONTINUE
    52          WRITE(*,*) ''
    53   3200   RETURN
    54   3300   FORMAT(A$)
    55        END
    56
    57  10000 SUBROUTINE SPLIT(SRC,SEP,DEST,SPLIT_SIZE)
    58          CHARACTER*100 SRC,DEST(10)
    59          CHARACTER*1 SEP
    60          INTEGER IND,S1,S2,RSLT,SPLIT_SIZE
    61          IND = 0
    62          S2 = -1
    63          I = 1
    64  10100   RSLT = INDEX(SRC(IND+1:),SEP)
    65          IF(RSLT.NE.0) THEN
    66            S1 = IND + 1
    67            IND = IND + RSLT
    68            S2 = IND - 1
    69            DEST(I) = SRC(S1:S2)
    70            I = I + 1
    71            GOTO 10100
    72          END IF
    73          DEST(I) = SRC(S2+2:)
    74          SPLIT_SIZE = I
    75          RETURN
    76        END
    77
takk@deb9:~$

FORTRANは、まだスラスラ書けるというわけではないですが、ようやくプログラミングらしくなってきた気がします。

実行結果です。

takk@deb9:~$ gfortran -fdollar-ok cut7.f
takk@deb9:~$ ./a.out -b1-5 x00
10010
10110
10210
10310
10410
10510
10610
10710
10810
10910
takk@deb9:~$ ./a.out -b1-3,5 x00
1000
1010
1020
1030
1040
1050
1060
1070
1080
1090
takk@deb9:~$ ./a.out -b1,3,5 x00
100
110
120
130
140
150
160
170
180
190
takk@deb9:~$

Fortran INDEXを連続で取得する

FORTRANのcutコマンド続きです。
-bオプションのバイト数を2桁以上に対応するには、-(ハイフン)の位置を特定しなければなりません。INDEX関数を使って特定します。
INDEX関数の使い方は、引数に元の文字列と、検索文字列を指定すればよいです。

takk@deb9:~$ cat index.f
      CHARACTER*100 STRING
      STRING = '1,23,456,7890'
      IND = INDEX(STRING,',')
      WRITE(*,100) IND
  100 FORMAT(I3)
      END

takk@deb9:~$
takk@deb9:~$ gfortran index.f
takk@deb9:~$ ./a.out
  2
takk@deb9:~$

INDEX関数を二つ並べると、

takk@deb9:~$ cat index.f
      CHARACTER*100 STRING
      STRING = '1,23,456,7890'
      IND = INDEX(STRING,',')
      WRITE(*,100) IND
      IND = INDEX(STRING,',')
      WRITE(*,100) IND
  100 FORMAT(I3)
      END

takk@deb9:~$ gfortran index.f
takk@deb9:~$ ./a.out
  2
  2
takk@deb9:~$

このようにINDEX関数を二つ並べても結果は同じになります。次のカンマの位置はどう取得すればよいのでしょう。INDEXに開始位置はないものだろうか。

takk@deb9:~$ cat index.f
      CHARACTER*100 STRING
      STRING = '1,23,456,7890'
      IND = INDEX(STRING,',')
      WRITE(*,100) IND
      IND = IND + INDEX(STRING(IND+1:100),',')
      WRITE(*,100) IND
  100 FORMAT(I3)
      END

takk@deb9:~$

苦肉の策で部分文字列を抽出したものを検索元に置き換えてINDEXの引数に渡すことにしました。

takk@deb9:~$ gfortran index.f
takk@deb9:~$ ./a.out
  2
  5
takk@deb9:~$

カンマの位置は元文字列の’1,23,4~’と一致しました。結果は合ってそうです。
これをDOループで回してカンマの位置を全部取得するように修正します。

その前に検索しても見つからない場合は何が返るのでしょうか。検索文字列を変更して確認します。

(省略)
      IND = INDEX(STRING,':')
(省略)
takk@deb9:~$ ./a.out
  0
takk@deb9:~$

結果は0になりました。0ならループを終了するように作れば良いですね。

takk@deb9:~$ cat index.f
      CHARACTER*100 STRING
      STRING = '1,23,456,7890'
      IND = 0
  100 RSLT = INDEX(STRING(IND+1:),',')
      IF(RSLT.EQ.0) GOTO 300
      IND = IND + RSLT
      WRITE(*,200) IND
      GOTO 100
  200 FORMAT(I3)
  300 STOP
      END

takk@deb9:~$

発見されたかのチェックのため一旦結果をRSLTに格納して、後からINDに足すようにしました。
結果です。

takk@deb9:~$ gfortran index.f
takk@deb9:~$ ./a.out
  2
  5
  9
takk@deb9:~$

Fortran splitを作る(その1)

splitといってもsplitコマンドではなく、文字列を区切り文字で分割するsplit関数のようなことができる関数をFORTRAN77でも作りたいと思います。

前回作ったプログラムをベースに、カンマ毎に文字列を抽出するプログラムを作ってみました。

takk@deb9:~$ cat split.f
      CHARACTER*100 SRC
      CHARACTER*10 DEST(10)
      INTEGER I,S1,S2,SRC_SIZE
      SRC = '1,23,456,7890,ABCDE,FGHIJK'
      IND = 0
      I=1
  100 RSLT = INDEX(SRC(IND+1:),',')
      IF(RSLT.NE.0) THEN
        S1=IND+1
        IND = IND + RSLT
        S2=IND-1
        DEST(I) = SRC(S1:S2)
        I = I + 1
        GOTO 100
      END IF
      DEST(I) = SRC(S2+2:)
      SRC_SIZE = I

      DO 400 I=1,SRC_SIZE
      WRITE(*,200) DEST(I)
  400 CONTINUE
  200 FORMAT(A)
  300 STOP
      END

takk@deb9:~$

ではこれを関数化します。splitされた文字列を配列に格納したいので、SUBROUTINEを使います。

takk@deb9:~$ cat split.f
      PROGRAM MAIN
      CHARACTER*100 STRING
      CHARACTER*10 SSTRING(10)
      INTEGER I,S1,S2,SSIZE
      STRING = 'A,BB,CCC,DDDD,EEEEE,FFFFFF,GGGGGGG'
      CALL SPLIT(STRING,SSTRING,SSIZE)
      DO 100 I=1,SSIZE
      WRITE(*,200) SSTRING(I)
  100 CONTINUE
  200 FORMAT(A)
  300 STOP
      END

      SUBROUTINE SPLIT(SRC,DEST,SPLIT_SIZE)
        CHARACTER*100 SRC
        CHARACTER*10 DEST(10)
        INTEGER IND,S1,S2,RSLT,SPLIT_SIZE
        IND = 0
        I = 1
20100   RSLT = INDEX(SRC(IND+1:),',')
        IF(RSLT.NE.0) THEN
          S1 = IND+1
          IND = IND + RSLT
          S2 = IND-1
          DEST(I) = SRC(S1:S2)
          I = I + 1
          GOTO 20100
        END IF
        DEST(I) = SRC(S2+2:)
        SPLIT_SIZE = I
        RETURN
      END
takk@deb9:~$

カンマ限定のsplit関数になってしまいました。
結果です。

takk@deb9:~$ gfortran split.f
takk@deb9:~$ ./a.out
A
BB
CCC
DDDD
EEEEE
FFFFFF
GGGGGGG
takk@deb9:~$

Fortran デバッグする

デバッグ

前回のプログラムはバグってますので、デバッグしたいと思います。
gfortranというコンパイラを使ってますが、gがつくぐらいですからGDBも使えるでしょう。試しに-gをつけてビルドしてみます。

takk@deb9:~$ gfortran -g split2.f
takk@deb9:~$

すんなり通りました。
gdbを起動します。

takk@deb9:~$ gdb a.out
GNU gdb (Debian 7.12-6) 7.12.0.20161007-git
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a.out...done.
(gdb)

動きましたね。問題はここから。C言語の場合は、break mainとかしてmain関数にブレイク設定してからrunすれば良いですが、FORTRANはどこで止めればよいのでしょう。
とりあえずlコマンドでソースを見てみます。

(gdb) l
1             PROGRAM MAIN
2               CHARACTER*100 STRING
3               CHARACTER*10 SSTRING1(10),SSTRING2(10)
4               INTEGER I,SSIZE1,SSIZE2
5
6               STRING='1-3,5,7-12,13,14'
7               WRITE(*,*) STRING
8
9               CALL SPLIT(STRING,',', SSTRING1,SSIZE1)
10              WRITE(*,1100) SSIZE1
(gdb)

なるほど。行番号が表示されてますね。きっとこの番号を指定すれば、ブレイクポイントを設定できます。

(gdb) b 6
Breakpoint 1 at 0xcd9: file split2.f, line 6.
(gdb)

いけましたね。runします。

(gdb) run
Starting program: /home/takk/a.out

Breakpoint 1, MAIN__ () at split2.f:6
6               STRING='1-3,5,7-12,13,14'
(gdb)

ブレイクポイントで停止しました。
STRING変数を確認します。設定前ですので、ゼロでしょうか。

(gdb) print string
$1 = '\340\344\377\377\377\177\000\000\266\316\376\366\377\177\000\000 ?\367\377\177\000\000\266\316\376\366\377\177\000\000 ?\367\377\177\000\000\000\000\000\001', '\000' <repeats 56 times>
(gdb)

ゼロではなく初期化されていないので元のメモリの値が表示されました。
ステップ実行してみます。

(gdb) next
7               WRITE(*,*) STRING
(gdb)

再度STRING変数を確認します。

(gdb) print string
$2 = '1-3,5,7-12,13,14', ' ' <repeats 84 times>
(gdb)

初期化されて想定通りの文字列が格納されました。
この調子でデバッグしていけば、バグの原因も分かりそうです。

自作split関数のデバッグ 1

ではFORTRANの自作split関数をGDBデバッグしていきます。

splitする文字列はこれです。

 STRING='1-3,5,7-12,13,14'

まず、カンマでsplitして、次にハイフンでsplitします。
結果は、このようになりました。

takk@deb9:~$ ./a.out
 1-3,5,7-12,13,14                                                               
SSIZE1=  5
[1-3       ]
  SSIZE2=  5
[5         ]
  SSIZE2=  4
[7-12      ]
  SSIZE2=  4
[13        ]
  SSIZE2=  3
[14        ]
  SSIZE2=  4
takk@deb9:~$

カンマは4箇所ありますのでsplitすると、split後の文字列の個数SSIZE1が5となります。
その後、1-3に含まれるハイフンでsplitすると、SSIZE2は2となるはずなのですが、なぜか5と表示されています。これをデバッグします。
-gオプションをつけてビルド後、GDBを起動します。

takk@deb9:~$ gfortran -g split2.f
takk@deb9:~$ gdb a.out

~

(gdb)

listコマンドでソースの1行目~20行目を確認します。

(gdb) l 1,20
warning: Source file is more recent than executable.
1             PROGRAM MAIN
2               CHARACTER*100 STRING
3               CHARACTER*10 SSTRING1(10),SSTRING2(10)
4               INTEGER I,SSIZE1,SSIZE2
5
6               STRING='1-3,5,7-12,13,14'
7               WRITE(*,*) STRING
8
9               CALL SPLIT(STRING,',', SSTRING1,SSIZE1)
10              WRITE(*,1100) SSIZE1
11
12              DO 100 I=1,SSIZE1
13
14                WRITE(*,1300) SSTRING1(I)
15
16                CALL SPLIT(SSTRING1(I),'-', SSTRING2,SSIZE2)
17                WRITE(*,1200) SSIZE2
18
19        100   CONTINUE
20       1100   FORMAT('SSIZE1=',I3)
(gdb)

ハイフン指定するときのSPLITの呼び出しでブレイクしたいので、b 16してrun

(gdb) b 16
Breakpoint 1 at 0xf5a: file split2.f, line 16.
(gdb) r
Starting program: /home/takk/a.out
 1-3,5,7-12,13,14                                                               
SIZE1=  5
[1-3       ]

Breakpoint 1, MAIN__ () at split2.f:16
16                CALL SPLIT(SSTRING1(I),'-', SSTRING2,SSIZE2)
(gdb)

SPLIT関数へ渡す引数を確認します。SSTRING1配列は初回はI=1ですので、SSTRING(1)について値を確認します。

(gdb) p sstring1(1)
$1 = '1-3       '
(gdb)

カンマで分割された文字列が格納されるので1-3となっています。ただ、1-3の後に配列サイズ分空白が入ってるのが以前から気になっていましたが、置いておきます。

次にsplit関数内を見ていくので、ステップインします。

(gdb) s
split (src=..., sep=..., dest=..., split_size=32767, _src=10, _sep=1, _dest=10)
    at split2.f:34
34              IND = 0
(gdb)

先ほど見たばかりですが、引数についてもう一度確認してみます。
引数として渡された文字列はsrc変数となっていますので、p srcします。

(gdb) p src
$2 = '1-3       5         7-12      13        14        \377\377\000\000\000\000\210v\335\367\377\177\000\000 IUUUU\000\000\300\345\377\377\377\177', '\000' <repeats 18 times>, '\340\344\377\377'
(gdb)

おかしいです。1-3だけのはずが、元の文字列すべて表示されてしまいます。
もしかして後に続く文字列は全部表示されているのでしょうか。

この場所でブレイクポイントを設定します。

(gdb) b
Breakpoint 2 at 0x555555554a71: file split2.f, line 34.
(gdb)

ブレイクポイントまで実行。

(gdb) c
Continuing.
  SIZE2=  3
[5         ]

Breakpoint 1, MAIN__ () at split2.f:16
16                CALL SPLIT(SSTRING1(I),'-', SSTRING2,SSIZE2)
(gdb)

同じようにsstring1(2)を確認。

(gdb) p sstring1(2)
$3 = '5         '
(gdb)

想定通りです。ではSPLIT関数内部に移動して再度引数を確認します。

(gdb) s

Breakpoint 2, split (src=..., sep=..., dest=..., split_size=3, _src=10,
    _sep=1, _dest=10) at split2.f:34
34              IND = 0
(gdb) p src
$4 = '5         7-12      13        14        \377\377\000\000\000\000\210v\335\367\377\177\000\000 IUUUU\000\000\300\345\377\377\377\177', '\000' <repeats 18 times>, '\340\344\377\377\003\000\000\000\005\000\000\000\002\000'
(gdb)

予想通り、次は1-3の文字列は表示されず、5と、それ以降の文字列が表示されました。
続く

自作split関数のデバッグ 2

続きです。

自作のSPLIT関数を呼び出しするときに、文字列
‘1-3 ‘を引数で渡しているのに関数内部の方宣言の文字列サイズが大きいことが原因っぽいですが、実験プログラムを作って確認します。

takk@deb9:~$ cat test.f
      PROGRAM MAIN
        CHARACTER*10 AA
        AA='ABCDEFGHIJ'
        CALL TEST(AA)
      END

      SUBROUTINE TEST(SRC)
        CHARACTER*100 SRC
        WRITE(*,*) SRC
        RETURN
      END
takk@deb9:~$

このようなプログラムになりました。TEST関数に渡す文字列サイズは10Byteで、TEST関数の引数はサイズを100Byteにしています。
ビルドします。

takk@deb9:~$ gfortran -g test.f
test.f:4:18:

         CALL TEST(AA)
                  1
Warning: Character length of actual argument shorter than of dummy argument ‘sr ’ (10/100) at (1)
takk@deb9:~$

実引数と仮引数のサイズが違うというwarningが出ました。
サイズを合わせれば、warningは出なくなるのでしょうか。

 CHARACTER*10 AA

10を100に変更して、

 CHARACTER*100 AA

再度ビルドします。

takk@deb9:~$ gfortran -g test.f
takk@deb9:~$

warningは取れました。
もしかして、SPLIT関数のプログラムもサイズが違うから動作がおかしいのかもしれません。
文字列のサイズを、10/100混在していたのを100で統一したプログラムです。

takk@deb9:~$ cat split3.f
      PROGRAM MAIN
        CHARACTER*100 STRING
        CHARACTER*100 SSTRING1(10),SSTRING2(10)
        INTEGER I,SSIZE1,SSIZE2

        STRING='1-3,5,7-12,13,14'
        WRITE(*,*) STRING

        CALL SPLIT(STRING,',', SSTRING1,SSIZE1)
        WRITE(*,1100) SSIZE1

        DO 100 I=1,SSIZE1

          WRITE(*,1300) SSTRING1(I)

          CALL SPLIT(SSTRING1(I),'-', SSTRING2,SSIZE2)
          WRITE(*,1200) SSIZE2

  100   CONTINUE
 1100   FORMAT('SSIZE1=',I3)
 1200   FORMAT('  SSIZE2=',I3)
 1300   FORMAT('[',A,']')
        STOP
      END

      SUBROUTINE SPLIT(SRC,SEP,DEST,SPLIT_SIZE)
        CHARACTER*100 SRC
        CHARACTER*1 SEP
        CHARACTER*100 DEST(10)
        INTEGER IND,S1,S2,RSLT,SPLIT_SIZE
        IND = 0
        S2=-1
        I = 1
20100   RSLT = INDEX(SRC(IND+1:),SEP)
        IF(RSLT.NE.0) THEN
          S1 = IND+1
          IND = IND + RSLT
          S2 = IND-1
          DEST(I) = SRC(S1:S2)
          I = I + 1
          GOTO 20100
        END IF
        DEST(I) = SRC(S2+2:)
        SPLIT_SIZE = I
        RETURN
      END
takk@deb9:~$

ビルドして実行します。

takk@deb9:~$ gfortran split3.f
takk@deb9:~$ ./a.out
 1-3,5,7-12,13,14                                                               
SSIZE1=  5
[1-3                                                                                                 ]
  SSIZE2=  2
[5                                                                                                   ]
  SSIZE2=  1
[7-12                                                                                                ]
  SSIZE2=  2
[13                                                                                                  ]
  SSIZE2=  1
[14                                                                                                  ]
  SSIZE2=  1
takk@deb9:~$

ぴったり数字が合いました。これを使えばcutの-bオプションが作れそうです。

自作split関数のデバッグ 3

SPLIT関数が直ったかどうか、コマンドにして確認してみます。

takk@deb9:~$ cat split3.f
      PROGRAM MAIN
        CHARACTER*100 ARGS
        CALL GETARG(1,ARGS)
        IF(ARGS(1:2).EQ.'-b') THEN
          CALL B_OPT(ARGS(3:))
        END IF
        STOP
      END

      SUBROUTINE B_OPT(B_OPT_STRING)
        CHARACTER*100 B_OPT_STRING
        CHARACTER*100 SSTRING1(10),SSTRING2(10)
        INTEGER I,SSIZE1,SSIZE2

        WRITE(*,*) B_OPT_STRING

        CALL SPLIT(B_OPT_STRING,',', SSTRING1,SSIZE1)
        WRITE(*,11100) SSIZE1
        DO 10100 I=1,SSIZE1
          WRITE(*,11300) SSTRING1(I)
          CALL SPLIT(SSTRING1(I),'-', SSTRING2,SSIZE2)
          WRITE(*,11200) SSIZE2
10100   CONTINUE
11100   FORMAT('SSIZE1=',I3)
11200   FORMAT('  SSIZE2=',I3)
11300   FORMAT('   STRING:',A)

        STOP
      END

      SUBROUTINE SPLIT(SRC,SEP,DEST,SPLIT_SIZE)
        CHARACTER*100 SRC
        CHARACTER*1 SEP
        CHARACTER*100 DEST(10)
        INTEGER IND,S1,S2,RSLT,SPLIT_SIZE
        IND = 0
        S2=-1
        I = 1
20100   RSLT = INDEX(SRC(IND+1:),SEP)
        IF(RSLT.NE.0) THEN
          S1 = IND+1
          IND = IND + RSLT
          S2 = IND-1
          DEST(I) = SRC(S1:S2)
          I = I + 1
          GOTO 20100
        END IF
        DEST(I) = SRC(S2+2:)
        SPLIT_SIZE = I
        RETURN
      END
takk@deb9:~$

使い方は、cutコマンドの-bオプションを指定するように./a.out -bの後に文字列を指定すればよいです。
テスト用のスクリプトを組みます。テスト内容は適当です。

takk@deb9:~$ cat split3-test.f
cmd=./a.out

$cmd -b1
echo

$cmd -b5
echo

$cmd -b1,2
echo

$cmd -b1,3,5
echo

$cmd -b2,4,5
echo


$cmd -b1-2
echo

$cmd -b2-5
echo

$cmd -b1-2,3
echo

$cmd -b1,2-4,5
echo

$cmd -b1-3,4-5
echo
takk@deb9:~$

スクリプト実行の結果に文字列のサイズ分空白で出てしまって見にくいので、空白はすべてtrでカットして実行しました。

takk@deb9:~$ . split3-test.sh | tr -d ' '
1
SSIZE1=1
STRING:1
SSIZE2=1

5
SSIZE1=1
STRING:5
SSIZE2=1

1,2
SSIZE1=2
STRING:1
SSIZE2=1
STRING:2
SSIZE2=1

1,3,5
SSIZE1=3
STRING:1
SSIZE2=1
STRING:3
SSIZE2=1
STRING:5
SSIZE2=1

2,4,5
SSIZE1=3
STRING:2
SSIZE2=1
STRING:4
SSIZE2=1
STRING:5
SSIZE2=1

1-2
SSIZE1=1
STRING:1-2
SSIZE2=2

2-5
SSIZE1=1
STRING:2-5
SSIZE2=2

1-2,3
SSIZE1=2
STRING:1-2
SSIZE2=2
STRING:3
SSIZE2=1

1,2-4,5
SSIZE1=3
STRING:1
SSIZE2=1
STRING:2-4
SSIZE2=2
STRING:5
SSIZE2=1

1-3,4-5
SSIZE1=2
STRING:1-3
SSIZE2=2
STRING:4-5
SSIZE2=2

takk@deb9:~$

いわゆるprintfデバッグというものでしょう。 結果も見にくいですが、一応合ってそうです。

自作split関数のデバッグ 4

GDBのスクリプトでテストします。
まず、対象プログラムからテスト用コードを取っ払います。

takk@deb9:~$ cat -n split4.f
     1        PROGRAM MAIN
     2          CHARACTER*100 ARGS
     3          CALL GETARG(1,ARGS)
     4          IF(ARGS(1:2).EQ.'-b') THEN
     5            CALL B_OPT(ARGS(3:))
     6          END IF
     7          STOP
     8        END
     9
    10        SUBROUTINE B_OPT(B_OPT_STRING)
    11          CHARACTER*100 B_OPT_STRING
    12          CHARACTER*100 SSTRING1(10),SSTRING2(10)
    13          INTEGER I,SSIZE1,SSIZE2
    14
    15          CALL SPLIT(B_OPT_STRING,',', SSTRING1,SSIZE1)
    16          DO 10100 I=1,SSIZE1
    17            CALL SPLIT(SSTRING1(I),'-', SSTRING2,SSIZE2)
    18  10100   CONTINUE
    19          STOP
    20        END
    21
    22        SUBROUTINE SPLIT(SRC,SEP,DEST,SPLIT_SIZE)
    23          CHARACTER*100 SRC
    24          CHARACTER*1 SEP
    25          CHARACTER*100 DEST(10)
    26          INTEGER IND,S1,S2,RSLT,SPLIT_SIZE
    27          IND = 0
    28          S2=-1
    29          I = 1
    30  20100   RSLT = INDEX(SRC(IND+1:),SEP)
    31          IF(RSLT.NE.0) THEN
    32            S1 = IND+1
    33            IND = IND + RSLT
    34            S2 = IND-1
    35            DEST(I) = SRC(S1:S2)
    36            I = I + 1
    37            GOTO 20100
    38          END IF
    39          DEST(I) = SRC(S2+2:)
    40          SPLIT_SIZE = I
    41          RETURN
    42        END
takk@deb9:~$

次に-gオプションをつけてビルド。

takk@deb9:~$ gfortran -g split4.f
takk@deb9:~$

次はGDBのスクリプトを作成します。GDBで使うコマンドを列挙したテキストファイルを作ればよいです。

takk@deb9:~$ cat test1
break B_OPT
set args -b1,2
run

next
echo "B_OPT_STRING:"
print B_OPT_STRING
echo "SSIZE1:"
print SSIZE1



set args -b1,2,3,4,5
run

next
echo "B_OPT_STRING:"
print B_OPT_STRING
echo "SSIZE1:"
print SSIZE1


set args -b1,2,3,4,5,7,8,9,10
run

next
echo "B_OPT_STRING:"
print B_OPT_STRING
echo "SSIZE1:"
print SSIZE1

takk@deb9:~$

GDBのスクリプト名はtest1としました。
実行は、-xオプションをつけてファイル名を指定します。

takk@deb9:~$ gdb a.out -x test1

実行すると、結果が表示されます。

Reading symbols from a.out...done.
Breakpoint 1 at 0x979: file split4.f, line 15.

Breakpoint 1, b_opt (b_opt_string=..., _b_opt_string=98) at split4.f:15
15              CALL SPLIT(B_OPT_STRING,',', SSTRING1,SSIZE1)
16              DO 10100 I=1,SSIZE1
"B_OPT_STRING:"$1 = '1,2', ' ' <repeats 95 times>, '\000\000'
"SSIZE1:"$2 = 2

Breakpoint 1, b_opt (b_opt_string=..., _b_opt_string=98) at split4.f:15
---Type <return> to continue, or q <return> to quit---

Enterキー待ちになるので、確認したら、Enterを押します。

---Type <return> to continue, or q <return> to quit---
15              CALL SPLIT(B_OPT_STRING,',', SSTRING1,SSIZE1)
16              DO 10100 I=1,SSIZE1
"B_OPT_STRING:"$3 = '1,2,3,4,5', ' ' <repeats 89 times>, '\000\000'
"SSIZE1:"$4 = 5

Breakpoint 1, b_opt (b_opt_string=..., _b_opt_string=98) at split4.f:15
15              CALL SPLIT(B_OPT_STRING,',', SSTRING1,SSIZE1)
16              DO 10100 I=1,SSIZE1
"B_OPT_STRING:"$5 = '1,2,3,4,5,7,8,9,10', ' ' <repeats 80 times>, '\000\000'
"SSIZE1:"$6 = 9
(gdb)

結果の続きが表示されます。

自作split関数のデバッグ 5

man gdbを見てみるとこのような一文がありました。

       GDB  では C, C++, Modula-2 などで書かれたプログラムのデバッグが行なえま
       す。 GNU Fortran コンパイラが完成すれば Fortran もサポートされます。

Fortranコンパイラが完成すればサポートされると書いてありますが、デバッグできててますね。FORTRANと書くとFORTRAN77以前のことで、Fortranと書くと90以降のことを言うらしいですが、90や95のプログラムでもGDB動かせるようです。

ちなみに使用しているGDBのバージョンはこれです。今は8があるのでちょっと古めです。

takk@deb9:~$ gdb --version
GNU gdb (Debian 7.12-6) 7.12.0.20161007-git

FORTRANプログラムの文字列変数をGDBで表示するとき、文字列が格納されていない領域が全部スペースなのでとても見にくいです。これをなんとか解消してデバッグしやすくならないでしょうか。

Breakpoint 1, b_opt (b_opt_string=..., _b_opt_string=98) at split4.f:15
15              CALL SPLIT(B_OPT_STRING,',', SSTRING1,SSIZE1)
(gdb) p B_OPT_STRING
$1 = '1,2,3,4,5', ' ' <repeats 89 times>, '\000\000'
(gdb) 

思いつくのは、先頭の空白の位置に0(NULL)を入れることぐらいです。
GDBから見ると普通の文字列が格納されているように見えます、きっと。
FORTRANのプログラムを、INDEXで空白を探し、代わりに0を格納するように改造してみます。

文字列に0を代入するのもエスケープシーケンスというものがあるかどうかも現状は分からないので、これまでで覚えたものを使ってやってみます。

takk@deb9:~$ cat -n split5.f
     1        PROGRAM MAIN
     2          CHARACTER*100 ARGS
     3          INTEGER*1 A(100),SP_LOC
     4          EQUIVALENCE (A,ARGS)
     5          CALL GETARG(1,ARGS)
     6          SP_LOC=INDEX(ARGS,' ')
     7          A(SP_LOC)=0
     8          IF(ARGS(1:2).EQ.'-b') THEN
     9            CALL B_OPT(ARGS(3:))
    10          END IF
    11          STOP
    12        END

3行目で文字列の途中にNULLを入れるための、配列変数Aを用意してます。
この配列変数AをEQUIVALENCEで文字列ARGSと結合させ、INDEXで見つけた空白の位置に、A(位置)=0とすることで、文字列ARGSに(GDBから見て)終端コードである空白を埋め込んでいます。

では-gオプションをつけて、ビルド後、GDBを起動してみましょう。

takk@deb9:~$ gfortran -g split5.f
takk@deb9:~$ gdb a.out

引数に”-b1,2,3,4,5″を設定し、mainでブレイクさせます。

(gdb) set args -b1,2,3,4,5
(gdb) b main
Breakpoint 1 at 0xdb4: file split5.f, line 12.
(gdb) run
Starting program: /home/takk/a.out -b1,2,3,4,5

Breakpoint 1, main (argc=2, argv=0x7fffffffe7e2) at split5.f:12
12            END
(gdb)

NULL埋め込み後、printfで文字列を確認します。

(gdb) step
MAIN__ () at split5.f:5
5               CALL GETARG(1,ARGS)
(gdb) next
6               SP_LOC=INDEX(ARGS,' ')
(gdb) n
7               A(SP_LOC)=0
(gdb) n
8               IF(ARGS(1:2).EQ.'-b') THEN
(gdb) printf "%s\n",&args
-b1,2,3,4,5
(gdb)

NULLを埋め込む前だと、いらない空白がたくさん出てしまっていましたが、必要な分だけ表示されました。

自作split関数のデバッグ 6

ナンセンスなことですが、早く覚えるためできるだけたくさんFORTRANのプログラミングをしていこうと思ってます。たまたま思いつきでFORTRANプログラミングできましたが、いつもうまくいくとは限りません。あまりFORTRANに根気を使うと疲れてしまって、勉強も続かないので、FORTRAN修得までの長い道のりのモチベーション維持のため、たまには使い慣れた言語を使ってみます。

さてFORTRANのプログラムを改造せずに、文字列をきれいに表示する方法として、今回はGDBでPythonスクリプトを使います。まずはPythonをGDBで使えるようにインストール。

root@deb9:~# apt-get install gdb-python2

これで、GDBでPythonスクリプト使えます。さっそく試してみましょう。

takk@deb9:~$ gdb a.out

~

(gdb) pi print 'HELLO'
HELLO
(gdb)

素晴らしい。これもうGDBも思いのままです。
GDBスクリプトの代わりにPythonスクリプトでGDB自動化してみましょう。こんなスクリプトを使います。

takk@deb9:~$ cat -n test1.py
     1  gdb.execute('set args -b1,2,3,4,5')
     2  gdb.execute('break B_OPT')
     3  gdb.execute('run')
takk@deb9:~$

executeの中の文字列は、GDBスクリプトそのままです。
FORTRANプログラムを-g付きでビルドして、GDB起動します。

takk@deb9:~$ gfortran -g split4.f
takk@deb9:~$ gdb a.out

さきほどのPythonスクリプトを実行しましょう。

(gdb) source test1.py
Breakpoint 1 at 0x979: file split4.f, line 15.

Breakpoint 1, b_opt (b_opt_string=..., _b_opt_string=98) at split4.f:15
15              CALL SPLIT(B_OPT_STRING,',', SSTRING1,SSIZE1)
(gdb)

さて、問題の残りが空白で埋められる文字列変数ですが、Pythonスクリプトを使って、きれいにトリムしたいと思います。

(gdb) p b_opt_string
$1 = '1,2,3,4,5', ' ' <repeats 89 times>, '\000\000'
(gdb)

さきほどのスクリプトに4~6行目を追加しました。FORTRAN77ではすぐには書けない文字列処理も、Pythonではお茶の子さいさい。

takk@deb9:~$ cat -n test2.py
     1  gdb.execute('set args -b1,2,3,4,5')
     2  gdb.execute('break B_OPT')
     3  gdb.execute('run')
     4  s=gdb.execute('p B_OPT_STRING',to_string=True)
     5  ss=(s.split("'"))[1]
     6  print ss
takk@deb9:~$

再度GDBを起動します。今度は、gdb起動時に、-xオプションでPythonスクリプトも一緒に実行してしまいましょう。

takk@deb9:~$ gdb a.out -x test2.py

~

Breakpoint 1 at 0x979: file split4.f, line 15.

Breakpoint 1, b_opt (b_opt_string=..., _b_opt_string=98) at split4.f:15
15              CALL SPLIT(B_OPT_STRING,',', SSTRING1,SSIZE1)
1,2,3,4,5
(gdb)

シングルクォートをPythonでスプリットしてリストの2番目を表示しました。
-bオプションで指定した文字列が、そのままさくっと表示されましたね。

コメント

タイトルとURLをコピーしました