GNU AWKでCSVファイルを楽々あつかう組込変数FPATと、関数のインダイレクト呼び出し

CodeZine / 2014年12月25日 14時0分

 AWKはレコードセパレーター(RS)に従いレコードを分割し、フィールドセパレーター(FS)に従いフィールドに分割する言語ですが、この手法ではCSVファイルでフィールド内にカンマを含むような場合に処理を行うことが困難でした。そこでGNU AWKでは新たに組込変数FPAT(フィールドパターン)というものを導入することで簡単に扱えるようになりました。今回はこの組込変数FPATを中心に解説していきます。

■組込変数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 ccc\",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に慣れてきた人には使いにくいかもしれませんが、行列計算や集計を行う際には便利でしょう。



CodeZine

トピックスRSS

ランキング