講座「文系のためのawkプログラミング入門」
関数を使う場合に知っておかなければならない重要な問題のひとつに、変数の有効範囲があります。ある変数がプログラム中のどこからでも参照できる、あるいは使うことができる場合、これをグローバル変数と呼びます。これに対して、ある変数がプログラム中の特定の範囲のなかでしか参照できないとき、これをローカル変数と呼びます。awkでは、変数は基本的に全てグローバル変数です。変数の有効範囲に制限はありません。例えば次のスクリプトを実行してみてください。
#ex19-1 BEGIN{ x = 3 y = 4 print "関数の実行前" print "x = " x print "y = " y global() print "関数実行の結果" print "x = " x print "y = " y } function global(){ x = 2*x # x を 2倍する y = 2*y # y を 2倍する return }
実行結果は次のようになります。
関数の実行前 x = 3 y = 4 関数実行の結果 x = 6 y = 8
BEGIN部で代入された、x と y の値が関数の中でそれぞれ2倍され、BEGIN部の中でもその変更の結果が反映されているのがわかります。ところが、次のように明示的に関数の引数として、x と y を渡した場合は結果が異なります。
#ex19-2 BEGIN{ x = 3 y = 4 print "関数の実行前" print "x = " x print "y = " y global(x,y) print "関数実行の結果" print "x = " x print "y = " y } function global(x,y){ print "関数の内部" x = 2*x y = 2*y print "x = " x print "y = " y return }
実行結果は次のようになります。
関数の実行前 x = 3 y = 4 関数の内部 x = 6 y = 8 関数実行の結果 x = 3 y = 4
この場合、関数の内部で、渡されたx と y の値を2倍したにも関わらず、BEGIN部ではその結果は反映されません。これは、関数の引数の独特な性質です。関数の引数は上で述べたローカル変数です。関数の引数の x と y と関数の外の、x と y は名前は全く同じですが、awkでは別の変数とみなされているのです。関数の引数の x と y は関数の内部で一時的に存在するだけです。関数の実行が終わればなくなってしまいます。awkでは関数の引数として値を渡すとき、コピーを作成して渡します。従ってそのコピーにどういう操作を加えても、元の数(変数)には何の変化もありません。例えば上のスクリプトをさらに書き換えて、関数実行後に、ローカル変数がどうなるかを示してみましょう。BEGIN部で、a と b を表示させてみるとどうなるでしょうか?
#ex19-3 BEGIN{ x = 3 y = 4 global(x,y) print "関数の実行後" print "x = " x print "y = " y print "a = " a print "b = " b } function global(a,b){ print "関数の内部" a = 2*a b = 2*b print "a = " a print "b = " b return }
結果は以下のようになるでしょう
関数の内部 a = 6 b = 8 関数の実行後 x = 3 y = 4 a = b =
関数の引数としての a と b は 関数の外側で使われている a と b とは全く無関係だということがわかります。関数の引数はローカル変数であるということは、関数を定義し使う場合に忘れてはならない原則です。次に、これについて別の角度から考えてみます。
関数は独立したプログラムの部品として、いろんなスクリプトから呼び出して使えます。このことを考えれば、関数の内部で用いられる変数はできるだけローカル変数にすべきだということになります。ローカル変数に指定せずに関数内部で変数を使った場合、関数の外部で使われている変数名と重なると思わぬ副作用を生じます。次のスクリプトの結果を予想してください。
#ex19-4 BEGIN{ for(i = 1; i <= 10; i++) { printLetters(i) } } function printLetters(k){ for(i = 1; i <=k; i++) { printf "a" } print "" return }
printLetters という関数は、引数として受け取った数値と同じ回数だけ、a という文字を連続して出力します。引数として与えられる数は、1から10までです。したがって結果は
a aa aaa aaaa aaaaa aaaaaa aaaaaaa aaaaaaaa aaaaaaaaa aaaaaaaaaa
となるはずです。
ところが実際に実行してみると
a aaa aaaaa aaaaaaa aaaaaaaaa
となります。これは、関数の中で使われている i の値が、外側の (つまりBEGIN部の) i に影響を及ぼすためです。このように、関数の内部の変数名が外側の変数名と同じ場合には関数内部の処理が外側に副作用をもたらします。awkの変数は基本的に全てグローバルとみなされるからです。同じ名前であれば同じ変数だとみなされるからです。こういう副作用を回避するためにawkでは特殊なローカル変数の設定方法を採用しています。すなわち関数の引数として記述することによって、関数内部の変数を全てローカル変数にしてしまう方法です。具体的には、関数の引数として、関数内部のローカル変数を列挙します。即ち、次のように関数を定義します。
# ex19-5 BEGIN{ for(i = 1; i <= 10; i++) { printLetters(i) } } function printLetters(k, i){ # i を関数の引数として書く for(i = 1; i <=k; i++) { printf "a" } print "" return }
関数の引数の中に、関数内部で使う変数 i を書いてしまいます。このとき、スペースを数個挿入して本来の引数と区別するのが習慣となっています。この結果、関数呼び出しでは引数は一つですが、関数の定義においては引数の数はそれよりも増えてしまいます。awkの開発者自身があまり美しくない方法と認めていますが、こうすることによって関数内部の変数が、外の変数に影響を及ぼすことを防ぐことができます。この例を実行することによってその効果を確めてください。
ついでに述べておけば、awkはC言語に似ていますが、全ての変数を基本的にグローバルとする点では、awkはC言語と全く性格が異なります。C言語だけではなく多くの近代的なプログラミング言語とも異なります。そのために関数定義のやり方が他の言語と比べてやや変則的になっています。どちらが使いやすいかは色んな意見がありえるでしょうが、いずれにしてもこの言語の仕様については正確に理解しておくべきですね。