編注1:指定された区切り文字で項目を分割するコマンド。
編注2:「シェル芸」とは、UNIXシェル(主にbash)のワンライナーを駆使して文字列加工を自由自在に操ることです。また、そのような能力をもつ人をシェル芸人と呼びます。
フィールドとは?
AWKは読み込んだテキストファイルをレコード(デフォルトでは行に相当)に自動的に分割しますが、さらにそのレコードをフィールドというものに分割します。このフィールドはデフォルトでは英文の単語に該当します。具体的には、1つのレコードの中を組込変数FS(Field Separator)で分割したものをフィールドと言います。
このフィールドはレコードの先頭から$1、$2、$3……と分割されていきます。さらに、現在処理中のレコードでのフィールド数は組込変数NF(Number of Filed)で定義されますので、レコードの末尾からは$NF、$(NF - 1)、$(NF - 2) ……と定義されます。シェルの引数と似ているため、混同しないように注意しましょう。
例えば、"This is a pen." という英文があったとすると、次のように分割されます。
This is a pen. $1 $2 $3 $4 $(NF - 3) $(NF - 2) $(NF - 1) $NF
これは、デフォルトで組込変数FSが連続するスペースまたはタブになっているため、英文のようにスペースで区切られたものを処理する場合には都合の良いものになっています。また、多くのUnix系OS上で動作するサービスのログファイルの構造は、AWKでも処理しやすいように、ほとんどのものがスペースやタブで区切られています。
よく利用するテキストファイルの代表として、カンマ(,)でデータが区切られたCSVファイルがありますが、組込変数FSにカンマ(,)を代入することで、CSVファイルを扱うことができます。
$ echo "a,b,c" | awk -F',' '{print $2}' b
AWKでは組込変数FSがしばしば変更されるためか、他の変数は-vオプションで指定するのに対し、組込変数FSだけは-Fオプションで直接指定できるようになっています。
ただし、CSVファイルのフォーマットには、データをダブルクォート(")で括るようなものや、改行を含むようなものもあります。そのような場合には、標準的なAWKではどちらも扱うことができません。GNU AWK(gawk)編注3であれば前者に限り新しく定義された組込変数FPATを定義することで対応できますが、ここでは割愛します。
編注3:GNUプロジェクトで開発されているAWK。オリジナルのAWKにない機能が追加されている。
少し不思議な例を挙げておきます。AWKの組込変数FSは、連続するスペースまたはタブであると書きました。では、正規表現で記述した際には、[ \t]+ と同義なのでしょうか? 実は違います。記事の文面からは分かりづらいかもしれませんが、先頭にスペースを含めたもので試すと分かります。
$ echo " a b c" | awk '{print $2}' b $ echo " a b c" | awk -F'[ \t]+' '{print $2}' a
では、組込変数FSのデフォルトは何なのでしょうか。実は、スペース1つで定義されています。
$ echo " a b c" | awk -F' ' '{print $2}' b
様々なファイルを読み込むような場合だと、組込変数FSを変更していくことがあります。ふたたびデフォルトに戻したいこともありますが、そのような場合に覚えておくと同時に、自分で組込変数FS を設定した場合には行頭がどうなるかを注意しましょう。
指定フィールドを抜き出す
AWKによるフィールド操作で最もよく使われるものが、特定のフィールドの抜き出しではないでしょうか。すでに何度か例として挙げていますが、単純にprint文の引数としてフィールドを指定するだけです。
$ echo "a b c" | awk '{print $2}'
もちろん、本連載の第1回で説明したように、$0に抜き出したいフィールドを指定するだけでも同じことができます。
$ echo "a b c" | awk '$0 = $2'
これは、レコード($0)に第2フィールドを代入することを意味しますが、AWKでは代入に成功します。代入の真偽は左辺値で決まりますので、代入された$0が数字の0(ゼロ)または空文字列でない限り、真になります。真の場合には {print $0} が省略されたものとみなされるため、結果として$2が代入された$0が表示されることになります。
もちろん、抜き出したいものが数字の0や空文字列である場合には偽になってしまうため、何も表示されません。
$ echo "0 1 2" | awk '$0 = $1' ←何も表示されない $ echo "0 1 2" | awk '{print $1}' 0
これは、cutコマンドでも同様のことができます。
$ echo "0 1 2" | cut -d' ' -f1 0
AWKのフィールドの抜き出しとcutコマンドの最大の違いは、フィールドの区切りに正規表現が使えるかどうかという点でしょう。前回、組込変数RSには正規表現が使えない(gawkを除く)と書きましたが、組込変数FSには正規表現を用いることができます。したがって、以下に例として挙げた日付や時刻の区切りであるスペースやコロン、スラッシュを、まとめて組込変数FSに指定することができます。
$ echo "2014/03/08 12:12:12" | \\ awk -F'[ :/]' '{print $2}' 03
指定フィールドを消す
指定したフィールドのみを削除するには、指定したフィールドに空文字列を代入します。
$ echo "a b c" | awk '{$2 = ""; print}' a c
その後の処理にも依存しますが、上記のaとcの間にはスペースが2つ入ってしまっていますので、注意が必要です。
これをパターンのみで記述すると次のようになることは、本連載の第1回でも説明しました。
$ echo "a b c" | awk '!($2 = "")' a c
また、フィールド数が少ない場合には、特定のフィールドを削除するよりも、消したいフィールド以外を表示したほうが、簡単で分かりやすいと思います。
$ echo "a b c" | awk '{print $1, $3}' a c
実は、このprint文で用いたカンマは特殊で、組込変数OFS(Output Field Separator)に置き換えられます。組込変数OFSはデフォルトではスペース1つです。
AWKにはフォーマットを指定して表示することができるprintf文もありますので、以下のようにしても同じ結果が得られます。
$ echo "a b c" | awk '{printf("%s %s\\n", $1, $3)}' a c
注意していただきたいのは、このprintf文のカンマは引数の区切りであり、組込変数OFSを示すものではないことです。カンマを組込変数OFSとして扱うのは、print文だけです。
AWKには、print文とprintf文の両方の出力方法が用意されていますので、ある程度長いプログラムを作成する際には統一したほうが良いでしょう。