AWK 基礎知識(その3)

視点を変えて、AWK固有の癖についてお話します。
「サブルーチン」と「配列変数の添え字」についてです。


先ずは、サブルーチンから・・・
ご存じのように「サブルーチン」とは、似たような動作をするルーチンを抜き出して独立させ、メインルーチンから必要なときに呼び出せるように記述したルーチンです。
上手くサブルーチンを活用すると、プログラムの見通しを良くすることが出来ます。
さらに、実績あるサブルーチンを他のプログラムに移植することで、バグ発生防止にもなります。

一方、「関数」(または「ファンクション」)と呼ばれるルーチンも良く使われます。
関数とは、サブルーチンと似たような構造を持ちますが、呼び出した側に演算した結果を返すことができます。

ここまでは他の言語と同じです。しかし、AWKでは「関数」しか定義することが出来ません。(「サブルーチン」の概念が無い)
では、「サブルーチン」はどのように記述するのでしょうか?
簡単なことです。関数定義したルーチンから値を返さなければ良いのです。
仮に値を返す関数があったとしても、返す値を無視すれば「サブルーチン」となります。
たとえば・・・

function keisan(a, b) {
  c = a + b;
  return c;
}
これは、引数として与えられた二つの数を加えた結果を返す関数です。「return」は続く文の結果を呼び出した側に返す命令です。
外部ルーチンからは、以下のようにkeisan関数を呼び出します。
print keisan(2, 3);
x = keisan(4, 5);
keisan(6, 7);
1行目では、関数が計算結果を返しますので、モニタには演算結果である「5」が表示されます。
同様に、2行目では変数 x に演算結果の「9」が代入されます。
ここまでが関数としての使用方法です。

厄介なのが3行目です。
3行目は関数呼び出しだけで何もしていません。この場合、エラーも発生しません。
ただし、呼び出す側で変数 c が使用されていると、演算結果の「13」が代入されているはずです。
これが「サブルーチン」として使用された例です。(サブルーチンでは「return文」は不要です。)

ここで「変数のスコープ」の話をしなければなりません。
「変数のスコープ」とは、その変数が有効なのはどの範囲であるか・・・と言うことです。

詳しいことは後回しにして・・・
上記の例では、変数 c はグローバル変数として扱われてしまいます。メインルーチンからも、関数(サブルーチン)からも一つの変数として扱われます。
もし、変数 c が関数の外で別の目的に使用されている場合は、keisan関数を呼び出した途端に書き換えられることになります。
大きなスクリプトでは、ちょっとした改造で別の関数またはサブルーチン内で使用している変数名を使ってしまうことがあります。
非常にバグ発見の困難な状況に追い込まれるでしょう。AWKのように宣言不要な言語の大きな欠点です。

keisan関数内のみで使用する変数 c は、以下のようにローカル変数として定義すればこのような事故を未然に防ぐことが出来ます。
AWKでは関数内部で使われない余分な引数があってもエラーとしません。そして、引数として定義した変数のスコープは関数内のみになります。
この例の場合、引数として変数 c を指定しておくとローカル変数となり、この関数内のみ有効な変数となります。
当然ですが、関数外の変数 c にはまったく影響を及ぼしません。
function keisan(a, b, c) {
  c = a + b;
  return c;
}
以上が「サブルーチン」のお話です。
ようするに、AWKではサブルーチンとして使用するか関数として使用するかはプログラマー次第というお話しでした。。。


次は、配列変数の添え字のお話し・・・
「配列変数の添え字」とは、配列変数に与える番号のことです。
他の言語(C言語など)では「0」から始まる番号を使用します。
理由は簡単です。
配列変数は「配列変数要素数×要素のバイト数」をひと塊としてメモリ内に確保されます。
そして、配列の何番目かはその塊の先頭からのオフセットとして指定するのです。
配列変数の先頭はオフセットゼロですから「0」を添え字とするのです。「0×要素のバイト数」ですから塊の先頭ですね。
二番目の要素は塊の先頭から「1×要素のバイト数」ですから、配列変数要素の添え字は「1」ですね。

AWKでは全ての配列は「連想配列」を使用します。メモリに固定的に配列変数分として確保していません。
ですからC言語のように各要素は固定長ではありませんし、添え字も数値ではなく文字列でも良いのです。
もし、配列変数の添え字として数値を使用したい場合は、経験的に「1」から始まる番号の方が便利です。(実際には「1」と言う文字列が添え字とされます。)

理由は・・・

  1. AWKが自動的に区切子で分解した最初の要素番号は「1」
    AWKには、ファイルからテキストを読み込む際に、予約変数 FS で指定した文字を区切子として分解しながら読み込む機能があります。
    最初に分解した要素は変数「$1」に記憶されます。次の要素が「$2」・・・
    これらの定義済予約変数はまさに配列変数そのものですね。
    自分が定義する配列変数も同様とした方がバグを生むことがが少ないでしょう。
  2. 文字列の先頭位置は「1」
    AWKの予約関数である substr() 関数は、文字列の指定番目の文字から指定長さの文字列を返す関数です。
    この関数では、文字列先頭の位置指定を「1」とします。
    index() 関数や match() 関数が返す文字位置も先頭位置は「0」ではなく「1」です。
・・・と言うことで、私は配列変数の先頭要素番号を「1」としています。
バグっても何らエラーを出さないことの多いAWKですので、何事にも「如何にバグを少なくするか」を考えた結果です。

<-->

戻る