awkであそぼ

awk便利です。おーく。

# awkであそぼ

ワンライナー野郎を極めるにはawkは便利ですが、それ以上にも使おうと思えば使えてしまうのがまたニクいところなので紹介します。

## とりあえず使ってみよう

```bash:テキストのN列目を全て取得
~$ awk '{print $4}' /var/log/apache/access.log
[18/Feb/2016:21:57:17
[18/Feb/2016:21:57:37
[18/Feb/2016:21:57:47
  :
```

こんなかんじに、ログ等の一定パターンで区切れるテキストデータを入力として、特定の列を抜き取ったり、幾つかピックアップして順番を変えたりするのがよくある使われ方かなと思います。

さらにここから別のコマンドに渡して統計を取るとかもよくある使われ方かなと思います。

```bash:Apacheのアクセスログからちゃちゃっと時間別アクセス数を統計する
~$ awk '{print $4}' access.log | cut -d ":" -f 1-2 | sort | uniq -c
     61 [18/Feb/2016:15
     38 [18/Feb/2016:16
     62 [18/Feb/2016:17
     60 [18/Feb/2016:18
     22 [18/Feb/2016:20
    116 [18/Feb/2016:21
```

よく使いますよね？ (ゴリ押し)

## セパレータの設定

そのままつかうと一行ずつ空白に分解していいかんじに処理できますが、-F オプションを使えばセパレータをタブ文字やカンマ等を始め、普通の文字列にでも任意のものに変更することができます。

```text:man
-F value       sets the field separator, FS, to value.
```

```bash:いろいろセパる
~$ awk '{print $4}' access.log | awk -F ":" '{print $1" - "$2}' | tail -1
[18/Feb/2016 - 21
~$ awk '{print $4}' access.log | awk -F "/" '{print $1" - "$2}' | tail -1
[18 - Feb
~$ awk '{print $4}' access.log | awk -F "Feb" '{print $1" - "$2}' | tail -1
[18/ - /2016:21:57:47
```

## {}の中は普通のプログラム言語です

{}の中はC言語っぽいかんじの普通によくあるプログラム言語だと思ってもらえればこの中でもいろいろできることにきっとキミは気づくはず。

```bash:printの代わりにprintfを使ってみる
~$ awk '{print $4}' access.log | ¥
   awk -F ":" '{printf "%05d\n", $4}' | tail -2
00037
00047
```

```bash:計算する
~$ awk '{print $4}' access.log | ¥
   awk -F ":" '{print $2"+"$3"+"$4"="$2+$3+$4}' | tail -2
21+57+37=115
21+57+47=125
```

## 変数を使い倒す

$1 とか使ってるのもトドのつまりは変数です。既存の変数を変更することもできるし自分で好きな変数を作ることもできます。

### 変数を使う

```bash:変数をつくる
~$ awk '{print $4}' access.log | ¥
   awk -F ":" '{sum=$2+$3+$4; print sum}' | tail -2
115
125
```

```bash:$1とかを変えちゃう
~$ awk '{print $4}' access.log | ¥
   awk -F ":" '{$4+=100; print $4}' | tail -2
137
147
```

さらにはコマンドラインから変数を渡すこともできます。

```text:man
-v var=value   assigns value to program variable var.
```

```bash:
~$ awk '{print $4}' access.log | ¥
   awk -F ":" -v OFFSET=300 '{$4 += OFFSET; print $4}' | tail -2
337
347
```

### 組み込み変数

調べたら結構いっぱいあったんですが、自動でセットされている変数でよく使えるものは以下の２つくらいかなと思います。

- NF ... 現在の行のフィールド数
- NR ... 現在の行数

ちょっとフォーマット崩れててデコボコしたテキストでも最後のフィールドを取ってきたいよ！ってときは `{print $NF}` みたいな使い方をすれば取得できますし、NR を使えば awk でいろいろしつつ `cat -n` みたいなことも一緒にできたりします。

## ていうかそもそも入力行を全部処理するコマンドじゃないんで...

manをよく見ると詳しい使い方が書いてあったり。

```text:man(適宜改行してます)
   1. Program structure
       An AWK program is a sequence of pattern {action} pairs and user function definitions.

       A pattern can be:
              BEGIN
              END
              expression
              expression , expression

       One, but not both, of pattern {action} can be omitted.
       If {action} is omitted it is implicitly { print }.
       If pattern is omitted, then it is implicitly matched.
       BEGIN and END patterns require an action.
```

* awkっていうのはパターンとアクションのペアとユーザ関数から成っているんだぜ
* でもパターンかアクションはどっちか省略することもできるよ
* アクションが省略されたら { print } って書いたことといっしょだよ
* パターンが省略されたら暗黙的にマッチするぜ！(直訳)
* でもBEGINとENDの時はちゃんとアクション書いてね

{}で今まで僕たちが書いてきたのはパターンを省略した形で、全部にマッチする意味でした。パターンを指定すると、条件に当てはまった行のみを処理することができます。

### パターンを利用する

例えば、アクセスログをChromeに絞ろうとしたら、正規表現を利用して絞り込むことができます。

```bash:
~$ awk '$0~/Chrome/{print}' access.log
# $0~ は省略可能
~$ awk '/Chrome/{print}' access.log
# ついでに {print} も省略可能
~$ awk '/Chrome/' access.log
```

これだけだとgrepをかませただけなのと変わらないですが・・。もっと細かく指定するなら Chrome かつ ステータスコードが404 っていうのを出そうしたら、

```bash:
~$ awk '($9~/^404$/ && /Chrome/)' access.log
```

ってな風に書くこともできます。

### パターンを複数利用する

パターン{アクション} のペアは何個でもかけるので、例えば `ステータスコードが404の時は時間を出して、200のときはメソッド名を出す` という
サンプルにしては無意味すぎる
ことをしようとしたら、

```bash:
~$ awk '($9~/^404$/){print $9" "$4} ($9~/^200$/){print $9" "$6}' access.log
200 "GET
200 "GET
404 [18/Feb/2016:21:45:23
200 "GET
200 "GET
```

みたいな芸当もできてしまいます。


この辺までくるともはやワンライナーレベルじゃなくね？っていう疑問につながりますが、それがawk沼への第一歩になります。まて次号！