組込変数FPATの導入
通常、AWKのフィールド分割は組込変数FSにより分割を行います。この思想はとても便利で、一般的なファイルを処理するのには十分でした。ところが次に示すとおり、CSVファイルをうまく扱えません。
$ echo 'aaa,"bbb,ccc",ddd' |\ > awk -F, '{print $2}' "bbb
CSVファイルには「フィールド内にカンマを含む場合にはフィールドをダブルクォートで括る」というルールがありますので、第2フィールドは"bbb,ccc"になるはずですが、うまく取得できていません。こうしたCSVファイルも簡単に扱えるようにする仕組みとして導入されたのが、組込変数FPAT(フィールドパターン)です。
組込変数FPATを用いると、フィールド内にカンマが含まれている場合でも、次のようにきれいに扱うことができます。
$ echo 'aaa,"bbb,ccc",ddd' |\ > gawk -v FPAT='([^,]+)|(\"[^\"]+\")' '{print $2}' "bbb,ccc"
組込変数FPATに記述するのは、フィールドの区切りのパターンではなく、フィールドそのもの(内容)のパターンです。上記の例では([^,]+)|(\"[^\"]+\")という正規表現をFPATに代入しています。この正規表現の意味は「カンマ(,)を含まない、またはダブルクォート(")で囲まれていて、その中にダブルクォートを含まない文字列」です。ちょっと複雑ですが、これによりCSVファイルを扱えるようになります。
しかし、このパターンでは、CSVファイルの規格にある「ダブルクォートで括られた中に改行を含めることが可能」というルールに対応することはできません。
$ echo -e "aaa,\"bbb\nccc\",ddd" |\ > gawk -v FPAT='([^,]+)|(\"[^\"]+\")' '{print $2}' "bbb ddd
このような中途半端な状態なのにCSVファイルを扱えると明言するのはおかしいのではないかという意見もありますが、gawkの開発は「完全を目指すのではなく9割をサクサクこなし、フィールドに改行を含むようなCSVファイルは専用のツールで処理すれば良い」という思想で進められました。特にメーリングリストなどに投稿された質問で多いものに対して機能追加されていく傾向にあり、日々便利なものになってきています。
同様なものとして、Apacheなどのログファイルがあります。User-Agent名は一般的にダブルクォートで括られますので、同じようにして取り出すことができます。次に示すのは、Apacheのログが/var/log/httpd/access_logにあり、ログの最後の項目がUser-Agentの場合です。
$ sudo tail -f /var/log/httpd/access_log |\ > gawk -v FPAT='([^ ]+)|(\"[^\"]+\")' '{print $NF} "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)" "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"'
また、この組込変数FPATを使ったpatsplit()関数も追加されました。patsplit()関数は分割する文字列のパターンを指定した文字列を分割する関数です。AWK標準のsplit()関数は分割する区切り(セパレータ)のパターンを指定するのに対し、patsplit()関数は分割した文字列のパターンを分割します。
$ echo 'aaa,"bbb,ccc",ddd' |\ > gawk '{patsplit($0,arr,"([^,]+)|(\"[^\"]+\")"); print arr[2]}' "bbb,ccc"
これにより、従来のAWKで扱いづらかったデータにも対応できるようになります。
多次元配列
従来のAWKも、疑似的に多次元配列を持つことはできました。この際はカンマが8進数表記で\034の文字列となり、配列のインデックスが連接として扱われるということは、本連載の前シリーズ「AWK処方箋」の最終回で説明しました。
$ awk 'BEGIN{arr[1, 2]="aaa";print arr[1, 2]}' aaa $ awk 'BEGIN{arr[1, 2]="aaa";print arr[1"\034"2]}' aaa
こうした疑似的な多次元配列でも十分便利でしたが、gawkでは正式な多次元配列(実際には連想配列なので、正しくは多次元連想配列)が導入されました。
$ gawk 'BEGIN{arr[1][2] = "aaa"; print arr[1][2]}' aaa
ただし、この多次元配列は従来の疑似多次元配列とは異なります。
$ gawk 'BEGIN{arr[1][2] = "aaa"; print arr[1, 2]}' (改行だけが表示される)
多次元配列を走査するにはfor文を用います。次のプログラムはその例です。isarray1.awkとして保存します。
BEGIN { arr[1][1] = 300; arr[2]["Apple"] = 500; arr[3][1, 2] = 700; for (i in arr) { for (j in arr[i]) { print i, j, arr[i][j]; } } }
実行してみます。
$ gawk -f isarray1.awk 1 1 300 2 Apple 500 3 12 700
また、配列arrが何次元配列かを返す関数としてisarray()関数が追加されました。isarray()関数を用いると、先のプログラムは次のように書けます。実行結果は同じになります。
BEGIN { arr[1][1] = 300; arr[2]["Apple"] = 500; arr[3][1, 2] = 700; for (i in arr) { if (isarray(arr[i])) { for (j in arr[i]) { print i, j, arr[i][j]; } } } }
この多次元配列は便利なのですが、次のような場合に気をつける必要があります。
$ gawk 'BEGIN {split("a b c", arr[1]); print arr[1][1]}' gawk: cmd. line:1: fatal: split: second argument is not an array
エラーになりましたが、こういう場合には最初にarr[1][1]を空として生成しておく必要があります。
$ gawk 'BEGIN {arr[1][1] = "";split("a b c", arr[1]); print arr[1][1]}' a
今までのAWKに慣れてきた人には使いにくいかもしれませんが、行列計算や集計を行う際には便利でしょう。