複雑なawk
プログラムは、多くの場合ユーザー関数を定義することによって単
純化できる。ユーザー定義関数は組み込み関数と同じ様に呼び出すことができる
(セクション 関数呼び出しを参照)けれども、関数そのものはきちんと定義しなければならな
い。
関数定義はawk
プログラムのルールの中ならどこにでも記述できる。だから、
awk
プログラムの一般的な形というのは、ルールとユーザー定義の並びと言え
る。
nameと言う名前の関数を定義するには次のようにする。
function name (parameter-list) { body-of-function }
nameは定義する関数の名前である。関数名として許されるのは変数名と同じで ある。文字、数字またはアンダースコアの並びで、ただし先頭に数字が来てはいけな い。関数名は配列名や変数名と同じ名前空間にある。
parameter-listは、カンマで区切られた関数の引数と (関数内の)ローカル変 数の名前のリストである。関数が呼ばれるときに引数の名前は、対応する呼びだし側 の変数の値を得る為に使われる。ローカル変数は空文字列に初期化される。
body-of-functionはawk
の文で構成されている。ここは関数定義で一番
重要な部分である。なぜなら、関数がどのように動作するかということを記述する部
分であるからである。引数名は関数本体で(その関数に対する)引数を扱うためにあ
る。ローカル変数は関数本体で一時的な値を扱うための場所を確保する。
仮引数名は文法的にはローカル変数名となんら変わる所はない。その代り、実引数の 値が三つ与えられたとすると、parameter-listの最初の三つが仮引数となり、 後の残りはローカル変数となる。
(関数定義部での)引数の数は、その関数が呼ばれるときの引数とは必ずしも同じ数で はなく、(そういった関数では)parameter-listの名前は本当の引数と、ロー カル変数がリスト中にある、あるいは省略された引数はデフォルトの値(空文字列) がセットされている状態で関数呼び出しが行われていると考えられる。
普通、関数を記述しようとするときには、引数としていくつ。ローカル変数としてい くつ名前を使うか分かっている。 引数とローカル変数の間には余計にスペースを入れ るという取り決めになっている。そうすることによって自分以外の人が、記述 された関数をどのように使うのか理解できる。
関数本体を実行している間、引数とローカル変数は(プログラム中に存在していれば)
同じ名前の変数を関数本体から見えないようにする。見えなくなった変数は関数の中
でアクセスすることはできない。なぜならローカル変数によって、名前によってその
(隠された)変数にアクセスすることができなくなっているからである。
awk
プログラム中の他の(隠されていない)変数は、関数の中でも通常通りに参
照したり変更したりすることができる
引数とローカル変数は関数の本体を実行している間だけ存在している。関数本体から 抜けると、隠されていた変数に再びアクセスできるようになる。
関数本体には関数呼び出しのような式を含めることもできる。また、関数呼び出しで 自分自身を直接、あるいは他の関数を経由して間接的に呼び出すこともできる。この ような動作が起きたとき、関数が再帰していると言う。
awk
では、関数の定義をその関数を使う前に定義しなくともよい。これは、
awk
がプログラムを実行する前にプログラム全体を読み込むからである。
awk
処理系の多くでは、function
というキーワード
を func
と省略して使うことが出来る。しかし、POSIX では function
の使用しか規定していない。これはある程度実用的な implications である。
gawk
は POSIX 互換モードで動作しているときには
(セクション awk
の起動を参照)、
次のような文で関数定義を行うことができない。
func foo() { a = sqrt($1) ; print a }
ルールとして定義され、個々のレコードで関数`foo'の戻り値と共に変数func(の
内容)を連接し、その結果を基として対応するアクションを実行する。このことはお
そらく望んだところではない。 (awk
はこの入力を文法的に正しいものとして
受け入れる。したがって、関数はawk
プログラムの中で定義される前に使われ
ることになるだろう)
次にユーザー定義関数の例を挙げる。数値を引数に取り、それを特定の書式で出力す
るmyprint
という関数を呼びだす。
function myprint(num) { printf "%6.3g\n", num }
myprint
を使ったルールの例を挙げよう。
$3 > 0 { myprint($3) }
このプログラムは、先ほど定義した書式にしたがって、入力されたレコードの三番目 のフィールドが正の数であるときに(そのフィールドを) 出力する。というものであ る。だから入力として次のデータを与えると、
1.2 3.4 5.6 7.8 9.10 11.12 -13.14 15.16 17.18 19.20 21.22 23.24
このプログラムの出力はこうなる。
5.6 21.2
次の関数は再帰関数の一例である。この関数は文字列を逆順に出力する。
function rev (str, len) { if (len == 0) { printf "\n" return } printf "%c", substr(str, len, 1) rev(str, len - 1) }
関数呼び出しは関数の実行を引き起こす。関数呼び出しは一つの式で あり、式としての値は関数が返す値である。
関数呼び出しは関数名とそれに続く(括弧に囲まれた)引数である。引数として、式を
書くこともできる。そのような式は呼び出しが実行されたときに評価が行われてその
値が実引数として扱われる。例えば、次のfoo
という関数の呼び出しでは引数
が三つある (最初の一つは文字列連接)。
foo(x y, "lose", 4 * z)
警告: 空白(スペースかタブ)を関数名と引数リストを囲んでいる左括弧 との間に入れてはいけない。もし間違って空白を入れてしまった場合、
awk
は 変数と、(括弧に囲まれた) 式との連接であると認識する。しかし、書かれている名 前は変数名ではなく関数名であるから、エラーが報告される結果となる。
関数が呼ばれるときに、関数に対してその実引数の値のコピーが渡される。これを 値呼び出し(call by value)と言う。呼びだし側は実引数に式として変数を 使うこともできる。しかし、呼び出される関数はそれを関知せず、単に引数の値があ るとだけ認識する。例えば次のようなコードでは、
foo = "bar" z = myfunc(foo)
myfunc
の引数として"変数 foo
"を渡すのではなく、(foo
の
値である)文字列"bar"
を渡している。と考えられる。
関数myfunc
が関数内のローカル変数の値を変更したとしてもそれは(対応し
ている引数も含めて)他の変数には一切影響しない。 myfunc
が次のような
ものであったとしよう、
function myfunc (win) { print win win = "zzz" print win }
最初の引数のwin
を変更しても、呼びだし側にあるfoo
には 影
響しない。myfunc
を呼び出すときのfoo
の役割はその値である
"bar"
を計算したときに終わっている。 win
がmyfunc
の外にも
あったとしても、関数の外にある win
の値を関数の中で変更することはでき
ない。これは、myfunc
を実行している間は外にあるwin
は隠されてい
るので見ることも変更することもできないからである。
しかし、関数の引数として配列を渡すときは、(配列は)コピーされない。配 列の場合、関数内で直接操作できるように配列そのものが渡される。このような呼び 出し形式は、通常参照呼び出し(call by reference)と呼ばれる。関数内部で( 引数として渡された)配列を変更すると、それは関数の外にも 影響する。 これは、自分が何をしているか、ということがきちんと解っていなければ とても危ないといえるだろう。 たとえば、
function changeit (array, ind, nvalue) { array[ind] = nvalue } BEGIN { a[1] = 1 ; a[2] = 2 ; a[3] = 3 changeit(a, 2, "two") printf "a[1] = %s, a[2] = %s, a[3] = %s\n", a[1], a[2], a[3] }
`a[1] = 1, a[2] = two, a[3] = 3'を出力する。なぜなら changeit
の
呼び出しで、a
の二番目の要素に "two"
を格納しているからである。
return
文
関数本体中にreturn
文を記述することができる。この文は関数を呼び出した
ところへの復帰を行う。このとき、return
の後に式を記述することによって
その値を伴って(呼びだし元に)復帰することも可能である。
return expression
expressionは省略可能である。省略された場合の戻り値は定義されておらず、し たがって何が返るか分からない。
全ての関数定義の終わりには戻り値が書かれていないreturn
文があるとみなさ
れる。制御が関数の終わりに達したときに、その関数は予期できない値を返す。
awk
はそのような関数の戻り値を使ったとしても、警告はしないだろう。単に、
予期できない結果となるだけだろう。
次に挙げるユーザー定義関数の例は、引数として渡された配列の中で最大の大きさを 持つ要素の値を返すというものである。
function maxelt (vec, i, ret) { for (i in vec) { if (ret == "" || vec[i] > ret) ret = vec[i] } return ret }
maxelt
を呼ぶには引数として、配列名を一つ渡す。ローカル変数のi
と ret
は引数として渡す必要はない。 maxelt
に対して二つ、あるい
は三つの引数を渡して呼び出しても構わないが、そうした場合結果が予期できないも
のとなるかもしれない。関数の引数リスト中でi
の前に置かれている余分なス
ペースは、 i
とret
が引数として扱わなくてよいということを表して
いる。あなたが関数定義をするときにもこのような書き方をして欲しい。
次に実際にプログラム中でmaxelt
関数を使ってみよう。このプログラムでは
配列に値を格納し、maxelt
を呼び出す。そして配列要素の中で最大の数値を
報告する。
awk ' function maxelt (vec, i, ret) { for (i in vec) { if (ret == "" || vec[i] > ret) ret = vec[i] } return ret } # 各レコードのすべてのフィールドをnumsにロードする { for(i = 1; i <= NF; i++) nums[NR, i] = $i } END { print maxelt(nums) }'
入力に次のデータを与える。
1 5 23 8 16 44 3 5 2 8 26 256 291 1396 2962 100 -6 467 998 1101 99385 11 0 225
プログラムは次の出力をするだろう
99385
これは入力で与えられた配列要素の中で一番大きいものである。