前回、フィルタコマンド「awk」には「grep」や「sed」にはない「列」の概念があるので、便利にデータを利用できるということを紹介した。さらに、awkはsedよりもプログラミング性が高く、より細かい処理を行えるようになっている。今回は、そうした処理を行う際の基本となる「変数」について取り上げようと思う。利用するサンプルデータは前回、日本郵政グループのサイトからダウンロード・加工した郵便番号のデータだ。

変数を使ってみる

前回紹介したスクリプトに、少し機能追加したものを次に示す。


#!/bin/sh

key="$1"
awk '
BEGIN {
        i=0
        printf("====================================================\n")
        printf("\t\t住所から郵便番号を検索\n")
        printf("====================================================\n\n")
        printf("検索キーワード: %s\n\n","'$key'")
        printf("候補\t郵便番号 住所\n")
}

$2$3$4~/'$key'/ {
        ++i;
        printf("%03d\t%d (%s%s%s)\n",i,$1,$2,$3,$4);
}

END {
        printf("\n  総候補数\t%d\n",i)
}
' KEN_ALL_ROME.SSV

このスクリプトでは、ヘッダとフッタにあたる部分を「BEGIN」および「END」というパターンのアクションで出力している。注目してほしいのは、BEGINのなかの「i=0」という記述だ。

awkでは変数を宣言する必要がないのだが、ここではわかりやすいようにBEGINのなかで変数iを使うことを「0を代入する処理をする」ことで明示的に記述している。awkの変数はグローバルなスコープを持つので、BEGINで宣言した(ように見せている)ものでも、ほかのパターンに一致したアクション部分で利用できるし、値も同じになっている。

これを実行すると、次のようになる。


% ./search_zip_1 次 郎
====================================================
                住所から郵便番号を検索
====================================================

検索キーワード: 次郎

候補      郵便番号 住所
001     9811526 (宮城県角田市神次郎)
002     9610091 (福島県白河市弥次郎窪)
003     9640808 (福島県二本松市木藤次郎内)
004     3212116 (栃木県宇都宮市徳次郎町)
005     9501433 (新潟県新潟市 南区次郎右エ門興野)
006     9591943 (新潟県阿賀野市次郎丸)
007     9391802 (富山県南砺市北野(次郎丸))
008     9188227 (福井県福井市次郎丸町)
009     9120067 (福井県大野市右近次郎)
010     9190745 (福井県あわら市次郎丸)
011     4313304 (静岡県浜松市 天竜区次郎八新田)
012     6760063 (兵庫県高砂市高砂町 次郎助町)
013     6408444 (和歌山県和歌山市次郎丸)
014     7711702 (徳島県阿波市阿波町 大次郎)
015     7992646 (愛媛県松山市神次郎町)
016     7871551 (高知県四万十市住次郎)
017     8140165 (福岡県福岡市 早良区次郎丸)
018     8900062 (鹿児島県鹿児島市与次郎)

  総候補数  18
%

追加したのは、出力する行ごとに変数「i」の値を増やしていって、何件一致したのかを記録しておく機能だ。awkは手続き型のプログラミング言語から見ると、動作の仕組みとスコープがややわかりにくいかもしれないが、とにかくこんな感じで変数を使うことができるようになっている。

配列を使ってみる

変数だけでなく、awkでは「配列」も使用できる。イモ臭いコードになるが、一致した対象を都道府県ごとに集計して、どの都道府県にどの程度一致したのかを調べる機能を追加してみよう。ここでは、次のように記述した。


#!/bin/sh

key="$1"
awk '
BEGIN {
        i=0
        n[0]="愛知県"; n[1]="青森県"; n[2]="秋田県"; n[3]="石川県";
        n[4]="茨城県"; n[5]="岩手県"; n[6]="愛媛県"; n[7]="大分県";
        n[8]="大阪府"; n[9]="岡山県"; n[10]="沖縄県"; n[11]="香川県";
        n[12]="鹿児島県"; n[13]="神奈川県"; n[14]="岐阜県"; n[15]="京都府";
        n[16]="熊本県"; n[17]="群馬県"; n[18]="高知県"; n[19]="埼玉県";
        n[20]="佐賀県"; n[21]="滋賀県"; n[22]="静岡県"; n[23]="島根県";
        n[24]="千葉県"; n[25]="東京都"; n[26]="徳島県"; n[27]="栃木県";
        n[28]="鳥取県"; n[29]="富山県"; n[30]="長崎県"; n[31]="長野県";
        n[32]="奈良県"; n[33]="新潟県"; n[34]="兵庫県"; n[35]="広島県";
        n[36]="福井県"; n[37]="福岡県"; n[38]="福島県"; n[39]="北海道";
        n[40]="三重県"; n[41]="宮城県"; n[42]="宮崎県"; n[43]="山形県";
        n[44]="山口県"; n[45]="山梨県"; n[46]="和歌山県";

        printf("====================================================\n")
        printf("\t\t住所から郵便番号を検索\n")
        printf("====================================================\n\n")
        printf("検索キーワード: %s\n\n","'$key'")
        printf("候補\t郵便番号 住所\n")
}

$2$3$4~/'$key'/ {
        ++i;
        printf("%03d\t%d (%s%s%s)\n",i,$1,$2,$3,$4);
        if ($2=="愛知県") t[0] += 1;
        else if ($2=="愛知県") t[1] += 1;
        else if ($2=="青森県") t[2] += 1;
        else if ($2=="秋田県") t[3] += 1;
        else if ($2=="石川県") t[4] += 1;
        else if ($2=="茨城県") t[5] += 1;
        else if ($2=="岩手県") t[6] += 1;
        else if ($2=="愛媛県") t[7] += 1;
        else if ($2=="大分県") t[8] += 1;
        else if ($2=="大阪府") t[9] += 1;
        else if ($2=="岡山県") t[10] += 1;
        else if ($2=="沖縄県") t[11] += 1;
        else if ($2=="香川県") t[12] += 1;
        else if ($2=="鹿児島県") t[13] += 1;
        else if ($2=="神奈川県") t[14] += 1;
        else if ($2=="岐阜県") t[15] += 1;
        else if ($2=="京都府") t[16] += 1;
        else if ($2=="熊本県") t[17] += 1;
        else if ($2=="群馬県") t[18] += 1;
        else if ($2=="高知県") t[19] += 1;
        else if ($2=="埼玉県") t[20] += 1;
        else if ($2=="佐賀県") t[21] += 1;
        else if ($2=="滋賀県") t[22] += 1;
        else if ($2=="静岡県") t[23] += 1;
        else if ($2=="島根県") t[24] += 1;
        else if ($2=="千葉県") t[25] += 1;
        else if ($2=="東京都") t[26] += 1;
        else if ($2=="徳島県") t[27] += 1;
        else if ($2=="栃木県") t[28] += 1;
        else if ($2=="鳥取県") t[29] += 1;
        else if ($2=="富山県") t[30] += 1;
        else if ($2=="長崎県") t[31] += 1;
        else if ($2=="長野県") t[32] += 1;
        else if ($2=="奈良県") t[33] += 1;
        else if ($2=="新潟県") t[34] += 1;
        else if ($2=="兵庫県") t[35] += 1;
        else if ($2=="広島県") t[36] += 1;
        else if ($2=="福井県") t[37] += 1;
        else if ($2=="福岡県") t[38] += 1;
        else if ($2=="福島県") t[39] += 1;
        else if ($2=="北海道") t[40] += 1;
        else if ($2=="三重県") t[41] += 1;
        else if ($2=="宮城県") t[42] += 1;
        else if ($2=="宮崎県") t[43] += 1;
        else if ($2=="山形県") t[44] += 1;
        else if ($2=="山口県") t[45] += 1;
        else if ($2=="山梨県") t[46] += 1;
        else if ($2=="和歌山県") t[47] += 1;
}

END {
        printf("\n県別集計\n")
        for (j=0; j<47; j++)
                if (0 != t[j])
                        printf("%s  \t%d件\n",n[j],t[j])
        printf("\n総候補数\t%d件\n",i)
}
' KEN_ALL_ROME.SSV

上記のコードではまず、BEGINのアクションで配列に都道府県名を代入している。そして、比較処理をメインのアクションのなかで行い、一致したらカウントアップするという流れだ。ご覧のとおり、カウントには「t」という配列をいきなり使い始めている。awkでは宣言が不要なので、こういう書き方ができるのだ。

集計した結果は、ENDのアクションで出力している。こちらもべたっとしたイモコードにして平べったく書いておこうかと思ったが、あまりにコードが長くなるのでやめておいた。for構文を使ってサクッとまとめてある。メインのアクションのなかもfor構文を使うと同様に短くなるのだが、それはまた別の機会に取り上げるとしよう。このコードを実行すると、次のようになる。


% ./search_zip_2 次郎
====================================================
                住所から郵便番号を検索
====================================================

検索キーワード: 次郎

候補      郵便番号 住所
001     9811526 (宮城県角田市神次郎)
002     9610091 (福島県白河市弥次郎窪)
003     9640808 (福島県二本松市木藤次郎内)
004     3212116 (栃木県宇都宮市徳次郎町)
005     9501433 (新潟県新潟市 南区次郎右エ門興野)
006     9591943 (新潟県阿賀野市次郎丸)
007     9391802 (富山県南砺市北野(次郎丸))
008     9188227 (福井県福井市次郎丸町)
009     9120067 (福井県大野市右近次郎)
010     9190745 (福井県あわら市次郎丸)
011     4313304 (静岡県浜松市 天竜区次郎八新田)
012     6760063 (兵庫県高砂市高砂町 次郎助町)
013     6408444 (和歌山県和歌山市次郎丸)
014     7711702 (徳島県阿波市阿波町 大次郎)
015     7992646 (愛媛県松山市神次郎町)
016     7871551 (高知県四万十市住次郎)
017     8140165 (福岡県福岡市 早良区次郎丸)
018     8900062 (鹿児島県鹿児島市与次郎)

県別集計
大分県     1件
神奈川県    1件
埼玉県     1件
島根県     1件
栃木県     1件
鳥取県     1件
長崎県     1件
兵庫県     2件
広島県     1件
福岡県     3件
福島県     1件
北海道     2件
宮崎県     1件

総候補数    18件
%

こうした出力をgrepコマンドとsedコマンドだけで得ようとすると、相当煩雑になるのだが、awkコマンドならこのとおり簡単だ。

例えば、前回、今回やったようなことは、スプレッドシートアプリケーションやUI/UX一体型のデータベース風アプリケーションを使っても実現できる。だが、フィルタコマンドの真価は、データ数が多くなってきたときに発揮されるのだ。データファイルのサイズが100MB、1GBと増えてくると、アプリケーションでは動作しないか、または動きがもっさりしすぎて実用に堪えなくなってしまう。

これがフィルタコマンドならば、結構サクサクいける。もちろん、コマンドを並べる順序や指定する内容で実行速度は異なってくるが、何をするにしても数分以上待たなければならないアプリに比べると、軽すぎてしょうがないほどだ。とにかく便利なのでぜひとも習得してほしいコマンドである。