すごいとは思わないけれど、なぜか気になるFORTRAN。FORTRAN学習の先には何かある予感がします。何もないかもしれないし、何物にも替えがたい悟りを得るかもしれない。学校でFORTRANの実習したときもそうだったのですが、FORTRAN、これで何ができるんだろう、と不思議を感じます。まあ、いろんなことができるんでしょうけど。若い時に真面目に勉強しておけば、今FORTRANやり直しすることもなかったかもしれませんが、その時にFORTRANで感動が起こらなかったのでしょうがないです。
当時言われていたFORTRANやCといった高級言語よりも、私はアセンブラが面白くて仕方がなかったので、高級言語をまったく学ぶ気がありませんでした。
今はコマンドにハマってますので、どちらかといったら高級なのでしょうけど。
FORTRAN77覚えようと思います。(90や95も)
世界初の高級言語なので、いつか使えるようになればいいと思っていましたが、今回から始めることにしました。 ちなみにFORTRANは、FORmula TRANslationが由来だそうです。
コンパイラはgfortranを使うことにします。95系77系両方使えます。
Contents
- LinuxでFortranことはじめ
- Fortran 書式付き表示
- Fortran 変数
- Fortran READ
- Fortran 関数
- Fortran ファイル書き込み
- Fortran ファイル読み込み
- Fortran 文字列
- Fortran 変数の結合
- Fortran seqコマンドを作る
- Fortran nlコマンドを作る
- Fortran echoコマンドを作る
- Fortran catコマンドを作る
- Fortran tacコマンドを作る
- Fortran revコマンドを作る
- Fortran cutコマンドを作る
- Fortran INDEXを連続で取得する
- Fortran splitを作る(その1)
- Fortran デバッグする
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オプションで指定した文字列が、そのままさくっと表示されましたね。
コメント