awk を使ってみよう

ちょっとしたdatファイルから必要な情報を抜き取ったり、パラメータ参照系コマンドの出力をいい感じに整形したりをawkコマンドでさくっとできちゃう人ってかっこいいですよね。僕もそんなかっこいい大人になりたいのでawkの使い方についてメモしていきます。
## 基本のキ
```bash
# awk の文法は基本的には以下のように書く。
# レコードごと(デフォルトだと1行ごと)に処理内容が適用される。
awk '{ 処理内容 }' 読み込ませるファイル名

# コマンドの実行結果をパイプでリダイレクトするのも良く使われる。
コマンド | awk '{ 処理内容 }'

# /etc/passwd の内容をそのまま表示する。$0はレコード全体を保持する変数。以下はどっちも同じ。
awk '{ print }' /etc/passwd
awk '{ print $0 }' /etc/passwd
 
# ファイルの内容と関係ない出力もできる。ファイルのレコード数だけ デュクシ が表示される。
awk '{ print "デュクシ" }' /etc/passwd
```

## フィールド変数
```bash

# 以下コマンドは /etc/passwd からユーザー名だけを抽出する。
# $1 はデリミタ(区切り文字)で分けられたフィールド(要素)のうち1番目のフィールドを保持する。
# -F オプションでデリミタを指定可能
awk -F":" '{ print $1 }' /etc/passwd

# 以下のようにすると1番目のフィールドと3番目のフィールドが繋がって表示される。
awk -F":" '{ print $1 $3 }' /etc/passwd

# スペースをいれるなら明示的に入れてやる必要がある。
awk -F":" '{ print $1 " " $3 }' /etc/passwd

# ちなみに以下のようにしても変数展開はしてくれない。そのまま"$1 $3"が出力される。 
awk -F":" '{ print "$1 $3" }' /etc/passwd
```

## 外部スクリプト
```bash

# 複数行にわたる複雑な処理をさせたいときは、スクリプトファイルを作成して -f オプションで指定する。
awk -f script.awk /etc/passwd
```
```myscript.awk
# このスクリプトファイルは awk -F":" '{ print $1 }' と同値
# ちなみに # でコメント
BEGIN { 
    FS=":" 
} 
{ print $1 }
```

## BEGINブロック/ENDブロック
```bash
# BEGIN{ 処理内容 } でレコードを読む前の初期処理を定義できる。
# END{ 処理内容 } でレコードを読んだ後の事後処理を定義できる。
awk 'BEGIN{ print "start" } { print $0 } END{ print "finished" }' /etc/passwd
```
## 正規表現とブロック
```bash
# ブロックの前に正規表現を記載することで、マッチしたレコードに対してのみ処理を実行することができる。
# /etc/passwd の root 行のみ表示させたいときは以下のようにする。
awk '/^root/ { print $0 }' /etc/passwd
 
# アンマッチはこのように書く。
awk '! /^root/ { print $0 }' /etc/passwd
```

## 式とブロック
```bash
# 正規表現以外にも、ブロックの前に boolen 式を書くことで特定レコードのみ処理させることもできる。
# /etc/passwd の root 行のみ表示させたいときは以下のようにする。
awk -F":" '$1 == "root" { print $0 }' /etc/passwd
 
# perl っぽい書き方もできる。
awk -F":" '$1 ~ /root/ { print $0 }' /etc/passwd
```

## 条件式
```bash
# if 文も使える。
awk -F":" '{ if($1 ~ /root/){ print $0 } }' /etc/passwd
 
# &&(論理積)とll(論理和)も使える。
awk -F":" '{ ($1 == "root") && ($7 == "/usr/bin/ksh") { print $0 } }' /etc/passwd
```

## 数値変数
```bash
# 数値も扱える。浮動小数点数演算も可能。
# awk の変数は全てストリングとして保持されるが、有効な数値ストリングであれば算術計算が可能。(※有効でない場合は0として計算される。)
# 以下コマンドは空白行をカウントする。
awk 'BEGIN{x=0} /^$/ {x=x+1} END{print "空白行は" x "行あるよ."}'

# 以下のような算術演算子も使える
#  i++, --i
#  a+=3, a^=2
```
## フィールド区切り文字
```bash
# フィールド区切り文字は -F オプション以外に、FS という固有変数で指定できる。
# 以下コマンドは awk -F":" { print $1 } /etc/passwd と同値。
awk 'BEGIN{FS=":"} { print $1 }' /etc/passwd

# 正規表現も使える
#  FS="¥t+"            1つ以上のタブ
#  FS="[[:space:]+]"   1つ以上のスペースあるいはタブ（デフォルト値）
```
## フィールド数
```bash
# NF はフィールド数を保持する固有変数。
awk -F":" 'NF < 8 { print $0 }' /etc/passwd

# $NF は最後のフィールドの値を保持するのでフィールド数が異なるレコードの最後の要素の抽出とかで便利。
awk -F":" '{ print $NF }' /etc/passwd
```

## レコード番号
```bash
# NR はレコード番号（行番号ではないので注意！）を保持している。
awk 'NR == 1 {print $0}' /etc/passwd
```

## レコード区切り文字（複数行に渡るレコードを扱う）
以下のような複数行で1レコードとして扱いデータがある場合。

```sample.dat
ピカチュウ
Lv.24
でんこうせっか

イシツブテ
Lv.80
かたくなる
```

```bash
# $1を名前、$2をレベル、$3をわざ名として扱いたいときは、以下のようにする。
# FS="¥n"によって各行をフィールドとして、RS="" とすることでブランク行をレコードの区切りとして認識する。
awk 'BEGIN{ FS="¥n"; RS="" } { print $1 "," $2 "," $3 }' sample.dat
```
## OFSとORS
```bash
# 変数をカンマで区切ることで、OFS変数(デフォルトは半角スペース)で指定された値を変数の間に差し込む。
awk -F":" 'BEGIN{ OFS="-" } { print $1,$2,$3 }' /etc/passwd
 
# ORSは print 文の最後に出力する文字を保持している。デフォルトは"¥n"
# 以下コマンドは各出力の間に空行をはさみたいときなどに使う。
awk -F":" 'BEGIN{ ORS="¥n¥n" } { print $0 }' /etc/passwd
```

## 複数行からタブ付き行への変換
スプレッドシート用に、レコードごとに1行のタブ区切り形式に変換したい場合には以下のようにする。長いからスクリプトでメモっとく。

```bash
awk -f sample.awk sample.dat
```

```sample.awk
BEGIN { 
    FS="\n" 
    RS="" 
    ORS="" 
} 
{  
    x=1
    while ( x<NF ) { 
            print $x "\t"   # ココの$xはフィールドを表す
            x++ 
    } 
    print $NF "\n" 
}
```

## ループ構成体
```bash
# while
awk '{ cnt=1; while(cnt=1){print "デュクシ"; cnt++} }' /etc/passwd

# do...while
awk '{ cnt=1; do{print "一度は実行するよ"} while(cnt!=1) }' /etc/passwd
 
# for(1レコードごとに4回出力させる）
awk '{ for(x=1; x<=4; x++){ print "iteration",x } }' /etc/passwd
 
# break/continue もある
awk '{ x=1; while(1){print "hello",x; if(x==5){ break } x++;} }' /etc/passwd
awk '{ for(x=1;x<=3;x++){ if(x==2){continue} print "hello",x }}' /etc/passwd
```

## 配列
```bash
# awk には配列もある。index は 1から始まるのが通例。
# for(x in list) で参照する場合、数字順に出力される保証はないので注意！！！
awk '{list[1]="apple"; list[2]="banana"; for(x in list){ print list[x] }}' /etc/passwd
 
# 連想配列も使える
awk '{list["Sato"]="No1"; list["Suzuki"]="No2"; for(x in list){ print list[x] }}' /etc/passwd
 
# 配列の要素を削除したいとき
delete list[1]
 
# 特定の配列要素が存在するか確認したいとき
if (1 in list) {
    print "あったよ"
} else {
    print "ございません"
}
```

## 出力フォーマット
```bash
# print 以外にも printf や sprintf が使える。
awk '{ printf("%s のレベルは %d だよ\n","ピカチュウ",24) }' /etc/passwd
awk '{ x=1; a="foo"; msg=sprintf("%s-%d",a,x) print msg }' /etc/passwd
```

## ストリング関数
```bash
# これはエラーする。awk はストリングを文字列の配列として扱えない。
awk '{str="How are you doing today?"; print str[2]}' /etc/passwd
 
# ストリングの長さを求める。
awk '{str="How are you doing today?"; print length(str)}' /etc/passwd
 
# 指定した文字列のインデックスを求める。
awk '{str="How are you doing today?"; print index(str,"you")}' /etc/passwd
 
# 小文字/大文字にする。
awk '{str="How are you doing today?"; print tolower(str)}' /etc/passwd
awk '{str="How are you doing today?"; print toupper(str)}' /etc/passwd
 
# 9番目から3文字表示させる。
awk '{str="How are you doing today?"; print substr(str,9,3)}' /etc/passwd
 
# 正規表現を使う場合は match() を使う。マッチした箇所の先頭 index を返す。一致しないと0を返す。
# RSTART: マッチした箇所の先頭 index を保持。
# RLENGTH: 文字内におけるその長さを保持。一致しないと-1を返す。
awk '{str="How are you doing today?"; print match(str,/you/),RSTART,RLENGTH' /etc/passwd
 
# ストリングの置換。最初のoを大文字に置換する。
awk '{str="How are you doing today?"; sub(/o/,"O",str); print str}' /etc/passwd
# gsubはマッチしたすべての文字を置換する。
awk '{str="How are you doing today?"; gsub(/o/,"O",str); print str}' /etc/passwd
 
# 文字列を指定のデリミタで分割して配列に格納する。関数は要素数を返す。
awk '{str="How are you doing today?"; e=split(str,word," "); print word[2],e}' /etc/passwd
```

## 参考
[IBM developerWorks - 実例でわかる awk: 第 1 回](https://www.ibm.com/developerworks/jp/linux/library/l-awk1/index.html?ca=drs-)
[IBM developerWorks - 実例でわかる awk: 第 2 回](https://www.ibm.com/developerworks/jp/linux/library/l-awk2/index.html?ca=drs-)
[IBM developerWorks - 実例でわかる awk: 第 3 回](https://www.ibm.com/developerworks/jp/linux/library/l-awk3/index.html?ca=drs-)