02 月 08 日(月)

フィルタ言語 AWK (3)

AWK の配列をうまく使えば,複雑な処理を簡単に記述できる場合がある.

AWK の第一歩 gawk 日本語マニュアル 等を参考にしながら, 作業を進めて行こう.


AWK の配列

AWK では,変数と同様に,配列についても宣言せずに利用できる. つまり,AWK の配列は, 動的配列(要素数が可変の配列)ということだ.

Cの配列は,基本的には固定配列だった. 動的配列を使うこともできたが, 準備の malloc( ) と 片付けの free() とが必要で, 面倒だった.

配列の簡単な使用例として次のスクリプト reverse.awk を試してみよう:

#!/usr/bin/awk -f

{ line[NR] = $0; }	# 配列 line[ ] に全レコードを記録

END {			# 配列 line[ ] の全要素を逆順で出力
	for (i = NR; i >= 1; i--) {
		print line[i];
	}
}

これは,レコードの順序を反転して出力する. たとえば,このスクリプトファイル自身を入力にして, 実行してみよう:

$  ./reverse.awk  reverse.awk

}
	}
		print line[i];
	for (...) {
...

#!/usr/bin/awk -f

AWK の連想配列

AWK は,データ型を持たず, すべてのデータを(基本的には)文字列として取り扱う. なんと,配列要素の添え字(要素番号)でさえも,文字列が利用される.

要素番号が文字列ということを理解するには, ちょっとした発想の転換が必要かもしれない. アタマを柔らかく.

このような文字列を添え字とする配列...連想配列は, 対応表(2フィールドのテーブル)データ等を処理対象とする場合, とても都合がよい. たとえば,食品の名前と色の対応表を次のように記述できる:

# AWK 版の対応表データ(連想配列)
color["apple"] = "red";
color["bacon"] = "pink";
color["coffee"] = "black";
...

「apple の色は red です」という意味.

考え方としては,「配列」というより,「構造体」に近いかも... 場合によっては,次のような使い方もあり得る:

apple["color"] = "red";
apple["shape"] = "sphere";
...

上の例をC言語で記述してみれば, 連想配列の便利さがよくわかるだろう. Cでは,次のように2つの配列を準備しなければならない:

// C 言語版の対応表データ(2配列)
name[0] = "apple";  color[0] = "red";
name[1] = "bacon";  color[1] = "pink";
name[2] = "coffee"; color[2] = "black";
...

「0番の物の名前は apple,0番の物の色は red です」という意味.

もちろん,構造体を使えば1つの配列でも実現できるが... 2つのメンバ変数を準備しなければならない:

item[0].name = "apple"; item[0].color = "red";
...

そして,たとえば,"coffee" の色を調べるには, Cの場合:

  1. 配列 name[ ] を検索して, "coffee" の識別番号 id = 2 を求める.
  2. color[id] を参照する.

という2段階の手順が必要となるが, 検索処理のコードを書くのは面倒だ.

一方,AWK の連想配列なら,color["coffee"] と書くだけで済む. 面倒な検索処理をわざわざ記述する必要はない. AWK が自動的に検索してくれるんだ.

ちなみに,連想配列の配列添え字が文字列でよいということは, 負の数字列でもよいし,実数の数字列でもよいということだ. しかし,数字列が数値化される場合もあるので,注意しよう. たとえば,a[1.0]a["1"]a[1] は, すべて同じ要素であるとみなされる. (つまり,AWK のデータ型は,文字列だけというわけでもない. データ型について完全に忘れてしまっていると, いつか,罠にハマるかもしれない.)

連想配列のすべての要素を参照するためには, 制御構造 for を次の形式で利用すればよい:

for ( 添字変数 in 連想配列 )

これは,bashfor-in と同様だ. たとえば,上の例の連想配列 color の要素をすべて出力するには, 次のようなアクションを記述すればよい.

for ( name in color ) {
	print name, color[name]
}

これを実際に確かめたければ, 次のようなスクリプトを作成し,実行すればよい:

#!/usr/bin/awk -f

BEGIN {
	color[...] = ...;	# 連想配列の初期化
	...
	for ( ... ) {		# 連想配列の出力
		print ...
	}
	exit
}
このスクリプトでは BEGIN ブロックしか実行しないので,入力データは不要...?? おー,なんとAWK では,このようにフィルタ以外のプログラムも書けてしまう.

解答例は こちら


本日の課題

投票データには, 米国大統領選挙における投票用紙に記入された候補者名が すべて記録されている.

もちろん,嘘.
このデータを集計処理するための AWK スクリプト count.awk を作成せよ. 処理結果としては, すべての候補者について得票数および得票率(%)を出力すること. スクリプトと実行結果をレポートせよ.

なお,当然のことだが, 入力データが他のものに変わっても使えるスクリプトであること. つまり, 入力データ中の文字列定数("Trump" 等の候補者名)を スクリプトに書き込まないこと.

ハードコーディング(www. 次回選挙で使えなくなるだろ!!

ヒント:

上級者向け改良案:

レポート提出 注意事項

おまけ

実は,データファイル vote.txt を生成するためにも AWK が利用されました.

ご参考まで.


(c) 2016, yanagawa@kushiro-ct.ac.jp