フィルタ系プログラムその他

−ファイルI/O,コマンドライン引数−

最も簡単なフィルタ系プログラム

標準入力からデータを受け取り,標準出力に結果を返すプログラムを,一般にフィルタという.UNIXではおなじみのsedcatなどもそうである.

研究においてはこのフィルタ系プログラムを多用する.もちろん自分で組めたほうがなお有効.

では,Fortranで似たようなことができるだろうか? もちろんできる.それも,いままで教えてきたことで充分できるのである.

たとえば,標準入力から10個の実数の列を読み込み,その平均と標準偏差を標準出力に出力するプログラム.:


% ./a.out
1 2 3 4 5
6 7 8 9 10
 5.5,  2.87228131

新しい要素:PARAMTER属性.変数宣言のときparameterをつけると,その変数は定数とみなされる.変更することができない.物理定数の定義などによく使われる.また,配列のサイズ指定に使えるというところがエラい(下記ex12b参照).

ちなみに,ループ一回で平均と標準偏差を両方求めるアルゴリズムを使っていることに注意.

つねに10個というのはセンスがない(笑)ので,ちょっと改良.今度は標準入力から整数n(<100)とn個の実数の列を読み込み,その平均と標準偏差を標準出力に出力するプログラム.nは入力データの最初の値とする:


% ./a.out
5
1 2 3 4 5
 3., 1.41421354

フィルタ系プログラムとシェルのリダイレクションを組み合わせると,柔軟な操作ができる.たとえば,ファイルdata1がこんな内容のとき,

10
1 2 3 4 5 6 7 8 9 10

上のex12bを次のように動かすことができる.

% ./ex12b < data1
 5.5,  2.87228131

これはパイプを使って次のようにも書ける.

% cat data1 | ./ex12b
 5.5,  2.87228131

あるいはファイルを使わず,直接,シェルのヒアドキュメントを使って,

% ./ex12b <<EOM
10
1 2 3 4 5 6 7 8 9 10
EOM
5.5,  2.87228131

ファイルI/O

一つのファイルからデータを受け取る場合は標準入力から受け取ることができるのだが,それでは複数のファイルからデータを受け取るにはどうすればよいか?

結論から言うと,自分でファイル操作を行う必要がある.

といってもこれは意外と簡単で,キーボードからの入力や画面への出力とそう大して変わらない.ちょっと手間が増えるだけである.

簡単のためにファイル一つの例を例示する.上の例と同様に,ファイルdata1から,整数nとn個の実数を受け取り,平均と標準偏差を出力する.ただし出力はファイルresult1に対して行なう.


% ./ex13
% cat ./result1
 5.5,  6.20483685

画面に一切メッセージを出さないプログラムであるが,catすると確かにresult1に結果が出力されていることがわかる.

ファイルからの読み込みの基本:

	open (ファイル番号,file=ファイル名, iostat=結果変数, &
            action='read', access='sequential', status='old')

	if (結果変数 /= 0) then
		きちんと読めなかったときの処理
	else
		read(ファイル番号,フォーマット)変数......
	endif

	close(ファイル番号)

要するにこれまで出てきたread文で読むのである.

ファイルへの書き出しの基本:

	open (ファイル番号,file=ファイル名, iostat=結果変数, &
            action='write', access='sequential', status='new')

	if (結果変数 /= 0) then
		きちんとファイルを開けなかったときの処理
	else
		write(ファイル番号,フォーマット)変数......
	endif

	close(ファイル番号)

これはこれまで出てきたprint文ではなく,write文で書く.

Open文の中にゴチャゴチャ入っているのは,こういう意味:

キー 値とその意味
(ファイル番号) 複数のファイルを開くとき,重複のないように各ファイルに割り当てる番号(整数).ちなみに「5」は標準入力,「6」は標準出力,「0」は標準エラー出力にすでに割り当てられている(これらについては,使うときはopen/closeの必要はない).
file ファイル名(文字列変数,または文字列定数)
iostat 結果を表す整数変数を指定する.エラー無しのとき0になる
action 'read' 'write' 'readwrite'のうち一つ
access 'sequential' 'direct'のいずれか.テキストファイルの場合は前者.バイナリファイルの場合は後者.指定しない場合は前者となる.
status 'old' 'new' 'replace'などがある.一般的に,読む場合はold,書く場合はnew

なおこのプログラムは,他にも次に示すようなテクニックをいろいろと使っている

コマンドラインから値を受け取る方法
(付録:文字列から数値を得る方法)

二つのファイルからデータを受け取り,答えを返すプログラムを考える.このとき,ファイル名を上の例ex.13のようにプログラム内に書いてしまう(これをハードコードという)と,違うファイルを扱うときはいちいちプログラム書き換え・再コンパイルが必要になる.

一つのファイルだけからデータを受け取るときは,上記ex12bのように標準入力から受け取るプログラムを書きシェルのリダイレクションを活用すればそのような事態を避けられるのだが,さて複数のファイルを扱う場合は?

これはたとえば,次ぎのようにすればよい

% ./a.out file1 file2 file3

つまり,シェルからコマンド名を入力するときに,その後ろにコマンドに与えたい値をずらずらと書いてしまうのである.これらの値をコマンドライン引数という.これを知ると知らないとでは天国と地獄ほども作業能率に差が出てくる.

プログラム側から,コマンドライン引数を知るにはどうするか?これは関数IARGCとサブルーチンGETARGを用いる.

まずは簡単な例として,二つの値をコマンドライン引数として受け取り,その和・差・積・商を出力するプログラムを提示する(ex14)


% ./ex14 4 5
     9.000    -1.000    20.000     0.800
% ./ex14 4 0
     4.000     4.000     0.000  (cannot divide by 0)
% ./ex14 5
 Error : Please give 2 numbers
 STOP

まず,与えられたコマンドライン引数の数は,組み込み関数IARGC()で得ることができる.

次に,n番目のコマンドライン引数は組み込みサブルーチンGETARG(n, text)でえることができる.答えは変数textに入る(textは,前もって定義しておくことを忘れないように).

が,ここで得られるのは文字列なのである.数値ではない.文字列を数値に変換するには?

そこで面白いテクニック,内部ファイルを使う.これは,ファイル読み込みのread文において,ファイル番号の代わりに文字列を入れると,ファイルではなくその文字列の内容を「読み込んで」くれるのである.上の例ではread(text, *) aがそう.

もう一つ面白いテクニックを使っていることに注意.readprint,writeのフォーマット指定文字列は,これまで「*」や固定した文字列(たとえば「'(3I, 4F5.3)'」)を使っていたが,じつはこれは,上の例のように変数を使ってもよい.これを使うと極めて柔軟なフォーマット制御ができるようになる.たとえばプリントする値の大きさに応じてフォーマットを変えるなど凝ったことができる.


AGATASHI