2.8. awk#

awk は、CSV や TSV ファイルなどのデータを処理するときに使うスクリプト言語の 1 つです。awk はコマンドや bash と同時に使うことができ、非常に手軽に利用できます。データ解析に先立って、平均や分散を見積もるときなどに便利です。ただし、本格的な解析を行う場合は、awk ではなく Python や R などの強力なプログラミング言語を利用するとよいでしょう。本ページでは awk の基本的な使い方を紹介します。

2.8.1. awk 基本文型#

awk の基本文型は、パターンとアクションを 1 組として並べる形式です。例えば、あるファイルの各行に対して、その行が指定したパターン(pattern)にマッチする場合に処理(action)を行う場合は、次のように記述します。

pattern { action }

パターンと処理が複数ある場合は、この基本文型を続けて書けばよいです。例えば、行の内容が pattern1 にマッチする場合は処理 1(action1)を行い、pattern2 にマッチする場合は処理 2(action2)を行う場合は、次のように書きます。

pattern1 { action1 }
pattern2 { action2 }

このように複数のパターンとアクションを使う場面として、例えば “apple” を含む行には処理 1、”orange” を含む行には処理 2 を行う、といった条件分岐をしたいときに利用できます。

2.8.2. パターン#

パターンは正規表現で指定します。例えば、ある行の内容全体に “apple” を含む場合に処理 1、”orange” を含む場合に処理 2 を行うには、次のように記述します。パターンマッチの対象は / で囲みます。

$0 ~ /apple/ { action1 }
$0 ~ /orange/ { action2 }

なお、$0 は組み込み変数(built-in variable)と呼ばれる特殊な変数で、1 行全体の内容が保存されています。$0 ~ /apple/ は、その行のどこかに “apple” という文字列が含まれているかどうかを確認しています。行のどこかに “apple” が見つかれば、action1 が実行されます。行全体($0)に対するパターンマッチの場合は、$0 ~ を省略することもできます。例えば、以下のように書くことも可能です。

/apple/ { action1 }
/orange/ { action2 }

CSV や TSV データには列(フィールド)の概念があります。例えば、2 列目のデータに “apple” を含む場合に処理 1、”orange” を含む場合に処理 2 を行うには、次のように 2 列目のみに対してパターンマッチを行います。$2 は組み込み変数で、2 列目を意味します。同様に、$3$4 などはそれぞれ 3 列目、4 列目を意味します。

$2 ~ /apple/ { action1 }
$2 ~ /orange/ { action2 }

正規表現によるパターンマッチ以外に、行番号を指定することもできます。例えば、11 行目以降のデータに対して処理を行う場合は、次のように記述します。NR は組み込み変数の 1 つで、現在読み込んでいる行番号が保存されています。

NR > 10 { action }

条件が複数ある場合は、AND 演算子 && や OR 演算子 || を用いて条件をつなげることができます。例えば、3 列目に “apple” を含み、かつ 10 行目以降である行に対して処理を行う場合は、次のように記述します。

$3 ~ /apple/ && NR > 10 { action }

2.8.3. アクション#

アクションでは、加減乗除などの計算処理や出力処理を行うことができます。また、必要に応じて ifwhilefor などのフロー制御構文も使用できます。ただし、フロー制御まで使う必要がある処理については、awk に固執するよりも Perl、Python、R などで書いた方が効率的です。そのため、ここでは awk のフロー制御については詳しく紹介しません。

アクションの書き方を単独で説明するよりも、実例を用いた方が理解しやすいため、以下では実例を使って説明します。

2.8.4. 使用例#

2.8.4.1. 列の抽出#

awk を使用して、ある条件を満たした行から特定の列のデータだけを抽出する例を示します。この操作は grepcut コマンドでも実現できます。ここでは、data/field_data ディレクトリにある iris.txt ファイルについて、”setosa” を含む行のうち 3 列目のデータを出力します。

cd
cd unix4bi/data/field_data
grep setosa iris.txt | cut -f 3
## 3.5
## 3
## 3.2
## ...
## ...

awk '/setosa/ { print $3 }' iris.txt
## 3.5
## 3
## 3.2
## ...
## ...

awk では、1 列目、2 列目、3 列目、… のデータはそれぞれ組み込み変数 $1$2$3、… に保存されます。3 列目のデータを出力したいので、アクションを print $3 と記述しています。

次に、同じく “setosa” を含む行のうち、2 列目から 5 列目までのデータを出力する例を示します。この場合、列と列の間をカンマで区切ります。カンマで区切ると、実際の出力では列の間が空白 1 つで区切られます。

awk '/setosa/ { print $2, $3, $4, $5 }' iris.txt
## 5.1 3.5 1.4 0.2
## 4.9 3 1.4 0.2
## 4.7 3.2 1.3 0.2
## ...
## ...

一方、カンマで区切らない場合は、列の間に空白が入らず、連結された状態で出力されます。また、各列の情報に加えて文字列を付け足したい場合は、">" のようにダブルクォートで囲んで指定します。

awk '/setosa/ { print ">" $2 $3 $4 $5 }' iris.txt
## >5.13.51.40.2
## >4.931.40.2
## >4.73.21.30.2
## ...
## ...

2.8.4.2. 平均の計算#

次に、awk を使って平均を計算します。data/field_data ディレクトリにある iris.txt ファイルについて、”setosa” を含む行の 3 列目のデータの平均を計算します。平均を求めるために、各行の値の合計と行数を、それぞれ変数 sumn に保存します。

awk '/setosa/ { sum = sum + $3; n = n + 1 } END { print sum/n }' iris.txt
## 3.428

awk '/setosa/ { sum = sum + $3; n = n + 1 } END { print "sum=" sum/n }' iris.txt
## sum=3.428

/setosa/ に対応する処理では、sum = sum + $3 で 3 列目の合計値を更新し、n = n + 1 で行数を数えています。

ここでは END という特別なパターンも使われています。END は、ファイル全体の処理が終わった後に、その付属アクションを実行することを意味します。この場合、すべての行を処理した後で sumn を用いて平均値 sum/n を計算しています。

同様に、4 列目および 5 列目についても平均を求めてみます。

awk '/setosa/ { sum = sum + $4; n = n + 1 } END { print sum/n }' iris.txt
## 1.462

awk '/setosa/ { sum = sum + $5; n = n + 1 } END { print sum/n }' iris.txt
## 0.246

複数の列を同時に扱う場合は、それぞれの列ごとに合計値を保存する変数を用意します。以下では、2 列目と 3 列目の平均を同時に求める例を示します。

awk '/setosa/ { sum2 = sum2 + $2; sum3 = sum3 + $3; n = n + 1 } END { print sum2/n, sum3/n }' iris.txt
## 5.006 3.428

列数が増えるとこの方法では煩雑になるため、for 構文を用いて繰り返し処理を行います。

awk '/setosa/ { for(i=2; i<6; i++){ sum[i] = sum[i] + $i }; n = n + 1 } END { for(i=2; i<6; i++){ print sum[i] / n } }' iris.txt 
## 5.006
## 3.428
## 1.462
## 0.246

このように単純な平均計算でもコードが複雑になるため、iffor などのフロー制御構文を多用する場合は、Perl、Python、R などの利用を検討するとよいでしょう。

2.8.4.3. 行番号で行を抽出#

行番号を指定して該当する行を抽出する例を示します。例えば、iris.txt ファイルの 20 行目から 30 行目までのデータを抽出する場合は、次のように記述します。この処理は headtail コマンドでも実現可能です。

awk '20 <= NR && NR <= 30 { print $0 }' iris.txt
## 20	5.1	3.8	1.5	0.3	setosa
## 21	5.4	3.4	1.7	0.2	setosa
## 22	5.1	3.7	1.5	0.4	setosa
## 23	4.6	3.6	1	0.2	setosa
## 24	5.1	3.3	1.7	0.5	setosa
## 25	4.8	3.4	1.9	0.2	setosa
## 26	5	3	1.6	0.2	setosa
## 27	5	3.4	1.6	0.4	setosa
## 28	5.2	3.5	1.5	0.2	setosa
## 29	5.2	3.4	1.4	0.2	setosa
## 30	4.7	3.2	1.6	0.2	setosa

ここで使っているパターンは 20 <= NR && NR <= 30 で、行番号が 20 以上 30 以下の行が対象となります。アクションとして print $0 を指定しているため、行全体が出力されます。awk では、行全体は $0 に、1 列目、2 列目、3 列目、… のデータはそれぞれ $1$2$3、… に保存されています。

2.8.4.4. 偶数行を抽出#

処理中の行番号は組み込み変数 NR に保存されています。この値が奇数か偶数かを判定することで、奇数行または偶数行のみを抽出できます。偶数行のみを抽出する場合は、NR を 2 で割った余りが 0 かどうかを調べます。

awk 'NR % 2 == 0 { print $0 }' iris.txt
## 2	4.9	3	1.4	0.2	setosa
## 4	4.6	3.1	1.5	0.2	setosa
## 6	5.4	3.9	1.7	0.4	setosa
## ...
## ...

2.8.4.5. 空行を削除#

空行とは、行の先頭と末尾の間に文字が存在しない行のことです。正規表現では、行の先頭は ^、末尾は $ で表されるため、空行は ^$ と記述できます。

行全体が特定のパターンにマッチする場合は、次のように $0 ~ /pattern/ または $0 を省略して /pattern/ と記述できます。

$0 ~ /pattern/ { action }
/pattern/ { action }

これに対して、パターンにマッチしない場合は ~ の代わりに !~ を使用します。例えば、空行パターン ^$ にマッチしない行のみを出力する場合は、次のように記述します。

awk '$0 !~ /^$/ { print $0 }' iris.txt