AWKのユーザ定義関数

スクリプトがちょっと大きくなってきたり、同じような処理を繰り返し書くこ とがあったら、 AWKでも関数を定義して使うことができます。関数の定義は 次の書式で書きます。

    function 関数名(引数の並び) {
        awkの命令
        awkの命令
        ...
        awkの命令
        return 戻り値    
    }

引数には数値でも文字列でも配列でもなんでも渡せます。returnの戻り値も、 数値と文字列が使えます。また再帰呼出しも出来ます。論より証拠、サンプル func.awkを見ましょう。

    #!/usr/local/bin/gawk -f
    # func.awk: 関数定義の例
    BEGIN {
        a = "OK"; b = "OK"; c = "OK";
        print foo(1,2);
        print a, b, c;                      # a,b,cは変化しない
        print bar("AWKは", "便利だ");
        print a, b, c;                      # a,b,cは変化しない
        print "4! == " recursive(4);        # 4! == 24
    }

    function foo(a, b,      c) {            # cは局所変数のつもり
        c = a + b;
        return c;
    }

    function bar(a, b,      c) {
        c = a b;
        return c;
    }

    function recursive(a) {                 # 再帰呼び出しも可能
        if (a <= 1)
            return 1;
        else
            return a * recursive(a-1);
    }

関数fooは数値を引数aとbで受けとって、その和を返す関数です。関数barは文 字列を引数aとbで受けとって、それを連結した文字列を返す関数です。

    % ./func.awk
    3
    OK OK OK
    AWKは便利だ
    OK OK OK
    4! == 24
    %

どちらも仮引数にa、b、cを使っていますが、呼び出し側に同じ名前の変数が あってもそれは変化しません。つまり、 AWKの関数は Cと同じで値渡しなのです。

ところで、呼び出しのところでfooもbarも引数cに何も渡していません。これ は AWK流儀の局所変数の宣言です。実際には AWKの関数には局 所変数がありません。 AWKの関数の引数が値渡しであることを利用して、局 所変数代わりにしているのです。

本当の引数a,bに続いて局所変数用の引数cを記述しますが、このサンプルのよ うに、この2種類の変数を人間が区別しやすくするために間にスペースをたく さん置くことが慣習となっています。

この局所変数もどきがうまく動くのは、 Cと違って定義と呼出し時の引数の 数が一致しているかどうかを AWKが確認しないからですが、これはある意味 で危険なことも承知しておいたほうが良いと思います。

さて、配列を引数として渡すときは少し様子が違います。 Cと同じで、配列 だけは参照渡しなので、関数内で配列要素を書き換えると呼び出し 側でも値が変わります。

    #!/usr/local/bin/gawk -f
    # func2.awk: 関数に連想配列を渡す。
    BEGIN {
        for (i = 0; i < 4; i++)
            a[i] = i;
        print "元の配列の内容を表示";
        for (i = 0; i < 4; i++)
            print i, a[i];
        foo(a);
        print "関数foo内で書換えると?";
        for (i = 0; i < 4; i++)
            print i, a[i];

        b["awk"] = "AWK"; b["perl"] = "PERL"; b["ruby"] = "RUBY";
        print "元の文字列";
        print b["awk"], b["perl"], b["ruby"];

        bar(b);
        print "関数bar内で細工すると";
        print b["awk"], b["perl"], b["ruby"];
    }

    function foo(a,         i) {
        for (i = 0; i < 4; i++)
            a[i] = -i;
    }

    function bar(a) {
        a["awk"] = reverse(a["awk"]);
        a["perl"] = reverse(a["perl"]);
        a["ruby"] = reverse(a["ruby"]);
    }

    # 文字列strを逆転したものを返す("abc" --> "cba")
    # str自体は変化しない。
    function reverse(str,           a, i, j, t) {
        j = length(str);
        if (j <= 0)
            return;
        split(str, a, //);                  # 文字列を配列に分解
        for (i = 0; i < j/2; i++) {         # 配列の中身を逆順にする
            t = a[i]; a[i] = a[j-i]; a[j-i] = t;
        }
        t = "";
        for (i = 0; i < j; i++)
            t = t a[i];                     # 配列を文字列に戻す
        return t;
    }

このスクリプトfunc2.awkを実行すると次のようになります。

    % ./func2.awk
    元の配列の内容を表示
    0 0
    1 1
    2 2
    3 3
    関数foo内で書換えると?
    0 0
    1 -1
    2 -2
    3 -3
    元の文字列
    AWK PERL RUBY
    関数bar内で細工すると
    KWA LREP YBUR
    %

OGURISU Osamu
2004-01-15