【AWK】ワンライナーでもファイル読み込みでも使えるスクリプト言語で、CSVからのデータ抽出/整形を自由自在に。【区切り文字は何でもOK】

# AWKとは
UNIX/LINUX系OSで使える、テキスト処理用のプログラム言語。
スペースなどの文字で区切られたファイルから2番めを抜き出す、などの操作が簡単に行えるので、CSVやTSV、ログファイルなどの整形や出力に威力を発揮します。例えば下記のようなファイルから

- 東京都の人だけ出力する
- メールアドレスだけ出力する
- 40歳以上の名前と年齢だけを出力する

などが行えます。

```
名前,ふりがな,アドレス,性別,年齢,誕生日,都道府県
奥,おく,oku@example.com,男,80,1939/7/7,群馬県
石垣,いしがき,ishigaki@example.com,女,77,1942/2/3,東京都
西尾,にしお,nishio@example.com,男,29,1990/2/4,埼玉県
笠原,かさはら,kasahara@example.com,女,20,1999/5/26,大阪府
斎藤,さいとう,saitou@example.com,男,43,1976/7/3,埼玉県
山中,やまなか,yamanaka@example.com,男,68,1951/10/23,山形県
荻原,はぎわら,hagiwara@example.com,女,50,1969/8/17,大分県
相馬,あいば,aiba@example.com,女,47,1972/11/28,福岡県
高瀬,たかせ,takase@example.com,男,67,1952/4/25,神奈川県
武田,たけだ,takeda@example.com,男,24,1995/4/17,栃木県
高村,たかむら,takamura@example.com,男,59,1960/12/4,長崎県
真矢,まや,maya@example.com,女,46,1973/3/15,滋賀県
井上,いのうえ,inoue@example.com,男,35,1984/9/30,沖縄県
大滝,おおたき,ootaki@example.com,男,20,1999/11/28,青森県
岩城,いわき,iwaki@example.com,女,68,1951/3/7,神奈川県
緒方,おがた,ogata@example.com,女,63,1956/5/19,東京都
中岡,なかおか,nakaoka@example.com,女,80,1939/6/29,秋田県
玉城,たまき,tamaki@example.com,男,38,1982/1/3,兵庫県
久保田,くぼた,kubota@example.com,男,47,1972/5/2,埼玉県
はしの,はしの,hashino@example.com,女,78,1941/6/3,兵庫県
益岡,ますおか,masuoka@example.com,女,66,1953/12/21,長野県
足立,あだち,adachi@example.com,男,62,1957/10/10,福井県
西岡,にしおか,nishioka@example.com,女,30,1989/9/30,千葉県
三浦,みうら,miura@example.com,女,76,1943/6/21,兵庫県
林田,はやしだ,hayashida@example.com,男,33,1987/1/7,埼玉県
鶴岡,つるおか,tsuruoka@example.com,女,51,1968/6/22,沖縄県
小日向,こひなた,kohinata@example.com,男,54,1965/8/27,兵庫県
荒木,あらき,araki@example.com,男,31,1988/4/17,神奈川県
若松,わかまつ,wakamatsu@example.com,女,66,1954/1/4,兵庫県
笹原,ささはら,sasahara@example.com,男,62,1957/6/6,愛知県
立川,たちかわ,tachikawa@example.com,女,40,1979/2/7,愛知県
緒方,おがた,ogata1@example.com,男,59,1960/10/30,埼玉県
劇団,げきだん,gekidan@example.com,男,25,1994/5/8,滋賀県
佐野,さの,sano@example.com,男,60,1959/6/17,愛知県
ともさか,ともさか,tomosaka@example.com,男,57,1962/9/22,大阪府
江川,えがわ,egawa@example.com,男,74,1945/5/7,長野県
浜口,はまぐち,hamaguchi@example.com,女,80,1939/10/27,東京都
長谷川,はせがわ,hasegawa@example.com,男,78,1941/5/30,兵庫県
杉本,すぎもと,sugimoto@example.com,女,38,1981/9/22,愛知県
首藤,すどう,sudou@example.com,女,67,1952/7/7,福岡県
黒木,くろき,kuroki@example.com,男,66,1953/8/26,愛知県
小松,こまつ,komatsu@example.com,男,30,1989/4/15,東京都
今野,いまの,imano@example.com,男,60,1959/4/15,福岡県
遠山,とおやま,tooyama@example.com,男,56,1963/11/5,鹿児島県
沢村,さわむら,sawamura@example.com,女,72,1948/1/4,北海道
平井,ひらい,hirai@example.com,女,61,1958/4/2,栃木県
深谷,ふかや,fukaya@example.com,女,58,1961/10/24,大阪府
久保田,くぼた,kubota1@example.com,男,48,1971/6/7,長崎県
大川,おおかわ,ookawa@example.com,女,45,1974/4/2,神奈川県
浅倉,あさくら,asakura@example.com,男,47,1972/3/18,東京都
```

※上記は、[なんちゃって個人情報](http://kazina.com/dummy/)で生成したダミーデータです。

ファイルを捜査し、指定したパターンにマッチする行に対して処理をかけていくので行指向プログラム言語とも呼ばれます。



# 実行方法

awkコマンドで実行します。

コマンド内にAWK文を含めて、ワンライナーで実行できます。

```bash
awk オプション 'AWKの処理' テキストデータファイル
```

例:

```bash
awk '{print $1}' data.dat
```

AWKスクリプトを外部ファイルにして実行もできます。

```
awk オプション -f AWKの処理が書かれたファイル テキストデータファイル
```

例:

```
awk -f myapp.awk scores.csv
```

# AWKプログラムの書式

AWKの処理が書かれたファイルをmyapp.awkとして保存して、スペースで区切られたusers.datというデータに対して処理をかけてみます。

```:myapp.awk
{
  print $3
}
```

```:users.dat
奥 おく oku@example.com 男 80 1939/7/7 群馬県
石垣 いしがき ishigaki@example.com 女 77 1942/2/3 東京都
西尾 にしお nishio@example.com 男 29 1990/2/4 埼玉県
笠原 かさはら kasahara@example.com 女 20 1999/5/26 大阪府
斎藤 さいとう saitou@example.com 男 43 1976/7/3 埼玉県
山中 やまなか yamanaka@example.com 男 68 1951/10/23 山形県
```

以下のように表示されます。

```:bash
awk -f 'myapp.awk' users.dat
oku@example.com
ishigaki@example.com
nishio@example.com
kasahara@example.com
saitou@example.com
yamanaka@example.com
```

要点は2つあります。

- {}で囲むと、全ての行に対して処理を実行する。
- $3と書くと、スペースで区切られた3列目の要素を対象にする。

行を**レコード**、列を**フィールド**と表現します。
各レコードに対して処理をかけ、フィールドを取り出して表示する処理がAWKの基本です。

今までは外部ファイルで見てきましたが、コマンドラインからのワンライナーでも記述方法は同じです。
{}で囲んで、その中でフィールドが指定されていることに注目してください。

```
awk '{ print $3 }'  users.dat
```

# レコードとフィールドの指定

下記のデータから、レコードとフィールドを指定して取り出してみます。
名前 ふりがな アドレス 性別 年齢 誕生日 都道府県　という想定です。

```:users.dat
奥 おく oku@example.com 男 80 1939/7/7 群馬県
石垣 いしがき ishigaki@example.com 女 77 1942/2/3 東京都
西尾 にしお nishio@example.com 男 29 1990/2/4 埼玉県
笠原 かさはら kasahara@example.com 女 20 1999/5/26 大阪府
斎藤 さいとう saitou@example.com 男 43 1976/7/3 埼玉県
山中 やまなか yamanaka@example.com 男 68 1951/10/23 山形県
```

## フィールドの指定

### 複数のフィールドを取り出す

特定のフィールドを取り出す方法は先述のとおりです。
複数取り出す場合は、,区切りで指定します。

```:bash 
awk '{ print $1, $3 }'  users.dat
奥 oku@example.com
石垣 ishigaki@example.com
西尾 nishio@example.com
笠原 kasahara@example.com
斎藤 saitou@example.com
山中 yamanaka@example.com
```

$0は特殊な指定で、全てのフィールドを取り出します。

```:bash 
awk '{ print $0 }'  users.dat
奥 おく oku@example.com 男 80 1939/7/7 群馬県
石垣 いしがき ishigaki@example.com 女 77 1942/2/3 東京都
西尾 にしお nishio@example.com 男 29 1990/2/4 埼玉県
笠原 かさはら kasahara@example.com 女 20 1999/5/26 大阪府
斎藤 さいとう saitou@example.com 男 43 1976/7/3 埼玉県
山中 やまなか yamanaka@example.com 男 68 1951/10/23 山形県
```

NF（Number of Fields）はフィールドの数を返します。
今回は7になります。
さらに、「\$」と組み合わせることができるので、\$NFという記述も可能です。
$NFは最後のフィールドになります。

```:bash 
awk '{ print NF, $NF }'  users.dat
7 都道府県
7 群馬県
7 東京都
7 埼玉県
7 大阪府
7 埼玉県
```

## レコードの指定

今までは全レコードに対して処理をかける方法を見てきましたが、特定のレコードだけを処理の対象にすることもできます。
{}の外に、条件を指定してレコードを絞り込みます。
条件は複数指定でき、正規表現も使えます。

3行目までを出力。
※NR（Number of Records）: 何行目のレコードかを示す。

```:bash
awk 'NR <= 3 { print $0 }'  users.dat
奥 おく oku@example.com 男 80 1939/7/7 群馬県
石垣 いしがき ishigaki@example.com 女 77 1942/2/3 東京都
西尾 にしお nishio@example.com 男 29 1990/2/4 埼玉県
```

3行目までの「男」を出力。（&&で複数条件をANDで指定。ここでは書きませんが、ORなど一通りの論理演算子が用意されています。）

```:bash
awk 'NR <= 3 && $4 == "男" { print $0 }'  users.dat
奥 おく oku@example.com 男 80 1939/7/7 群馬県
西尾 にしお nishio@example.com 男 29 1990/2/4 埼玉県
```

1990年代生まれを出力（正規表現）

```:bash
awk '$6 ~ /^199./ { print $0 }'  users.dat
西尾 にしお nishio@example.com 男 29 1990/2/4 埼玉県
笠原 かさはら kasahara@example.com 女 20 1999/5/26 大阪府
```

# 前処理（BEGIN）と後処理（END）

AWKは各レコードに処理をかける前/後にそれぞれ一度だけ処理をかけることができます。

```:users.dat
奥 おく oku@example.com 男 80 1939/7/7 群馬県
石垣 いしがき ishigaki@example.com 女 77 1942/2/3 東京都
西尾 にしお nishio@example.com 男 29 1990/2/4 埼玉県
笠原 かさはら kasahara@example.com 女 20 1999/5/26 大阪府
斎藤 さいとう saitou@example.com 男 43 1976/7/3 埼玉県
山中 やまなか yamanaka@example.com 男 68 1951/10/23 山形県
```

```:myapp2.awk
BEGIN {
  print "--- BEGIN ---"
}
{
  # comment
  print $0
}
END {
  print "--- END ---"
}
```

```:bash
--- BEGIN ---
awk -f myapp2.awk users.dat
奥 おく oku@example.com 男 80 1939/7/7 群馬県
石垣 いしがき ishigaki@example.com 女 77 1942/2/3 東京都
西尾 にしお nishio@example.com 男 29 1990/2/4 埼玉県
笠原 かさはら kasahara@example.com 女 20 1999/5/26 大阪府
斎藤 さいとう saitou@example.com 男 43 1976/7/3 埼玉県
山中 やまなか yamanaka@example.com 男 68 1951/10/23 山形県
--- END ---
```

各レコードの出力前で「--- BEGIN ---」、各レコードの出力後に「--- END ---」が出力されています。

なお、BEGINで使えるキーワードが用意されています。

- FS: Field Separator データファイルにあるフィールドの区切り文字（通常は半角スペースだが、他の文字に変更できる）
- RS: Record Separator データファイルにあるレコード区切り文字（通常は改行だが、他の文字に変更できる）
- OFS: Output Field Separator 出力するフィールドの区切り文字（通常は半角スペースだが、他の文字に変更できる）
- ORS: Output Record Separator 出力するレコードの区切り文字（通常は改行だが、他の文字に変更できる）

下記は、例えばCSVファイルのような,区切りデータ取り扱います。

```:users.csv
奥,おく,oku@example.com,男,80,1939/7/7,群馬県
石垣,いしがき,ishigaki@example.com,女,77,1942/2/3,東京都
西尾,にしお,nishio@example.com,男,29,1990/2/4,埼玉県
笠原,かさはら,kasahara@example.com,女,20,1999/5/26,大阪府
斎藤,さいとう,saitou@example.com,男,43,1976/7/3,埼玉県
山中,やまなか,yamanaka@example.com,男,68,1951/10/23,山形県
```

```:myapp3.awk
BEGIN {
  print "--- BEGIN ---"
  FS = ","
}
{
  print $1, $3
}
END {
  print "--- END ---"
}
```

```:bash
--- BEGIN ---
awk -f myapp3.awk users.dat
oku@example.com
ishigaki@example.com
nishio@example.com
kasahara@example.com
saitou@example.com
yamanaka@example.com
--- END ---
```

さらに下記のように、OFS、ORSを使って出力する区切り文字を変更できます。
もう少し工夫すれば、レコードを[]で囲めそうです。

```:users.csv
奥,おく,oku@example.com,男,80,1939/7/7,群馬県
石垣,いしがき,ishigaki@example.com,女,77,1942/2/3,東京都
西尾,にしお,nishio@example.com,男,29,1990/2/4,埼玉県
笠原,かさはら,kasahara@example.com,女,20,1999/5/26,大阪府
斎藤,さいとう,saitou@example.com,男,43,1976/7/3,埼玉県
山中,やまなか,yamanaka@example.com,男,68,1951/10/23,山形県
```

```:myapp4.awk
BEGIN {
  FS = ","
  OFS = "|"
  ORS = "]["
}
{
  print $1, $3
}
END {
}
```

```:bash
--- BEGIN ---
awk -f myapp4.awk users.dat
奥|oku@example.com][石垣|ishigaki@example.com][西尾|nishio@example.com][笠原|kasahara@example.com][斎藤|saitou@example.com][山中|yamanaka@example.com][
--- END ---
```

# awkコマンドのオプション

区切り文字は、先述のBEGINだけではなく、コマンドラインのオプションでも設定できます。
-Fオプションで、区切り文字を指定します。

```:bash
--- BEGIN ---
awk -F "," '{ print $1 }' users.csv
奥
石垣
西尾
笠原
斎藤
山中
--- END ---
```