GNU awkを使う

# はじめに
GNU awk(gawk)の使い方を説明します。ここでは単にawkといいます。
awkはテキスト処理のために設計されたプログラムです。

# 考え方
awkは1行毎に処理を行います。ファイルの最後まで処理をしたら終了します。
パターン(pattern)にマッチしたときに処理(action)を行います。

> pattern { action }

行毎の処理前、後におこなう処理は次のように指定できます。

> BEGIN { action }
> END { action }

# sample
lsで表示される情報を空白で区切った列を抜き出します。
lsした結果です。

```bash:console
$ ls -lsa | head
合計 252
 4 drwxr-xr-x 22 user user  4096 10月  5 02:09 .
 4 drwxr-xr-x  3 root root  4096  5月  7 23:55 ..
24 -rw-------  1 user user 24468 10月  5 02:09 .ICEauthority
 4 -rw-------  1 user user   201 10月  5 02:09 .Xauthority
 4 drwx------  3 user user  4096  8月 28 22:08 .anthy
 4 -rw-r--r--  1 user user   177  5月  7 23:51 .apport-ignore.xml
32 -rw-------  1 user user 31623 10月  5 02:01 .bash_history
 4 -rw-r--r--  1 user user   220  5月  7 23:48 .bash_logout
 4 -rw-r--r--  1 user user  3647  5月  7 23:48 .bashrc
```

lsの結果をpipeでawkに繋げて6列を表示する例です。

```bash:console
$ ls -lsa | awk '{ print $6 } ' | head

4096
4096
24468
201
4096
177
31623
220
3647
```

6列は$6で参照できます。

$6="" とすることで**6列を削除します。**

```bash:console
$ ls -lsa | awk '{ $6=""; print $0 }' | head
合計 264    
4 drwxr-xr-x 22 user user  10月 14 22:37 .
4 drwxr-xr-x 3 root root  5月 7 23:55 ..
28 -rw------- 1 user user  10月 14 22:30 .ICEauthority
4 -rw------- 1 user user  10月 14 22:30 .Xauthority
4 drwx------ 3 user user  8月 28 22:08 .anthy
4 -rw-r--r-- 1 user user  5月 7 23:51 .apport-ignore.xml
36 -rw------- 1 user user  10月 14 10:19 .bash_history
4 -rw-r--r-- 1 user user  5月 7 23:48 .bash_logout
4 -rw-r--r-- 1 user user  5月 7 23:48 .bashrc
```



次に"bash"を含む行を表示する例です。

```bash:console
$ ls -lsa | awk '/bash/ { print $0 } ' | head
32 -rw-------  1 user user 31623 10月  5 02:01 .bash_history
 4 -rw-r--r--  1 user user   220  5月  7 23:48 .bash_logout
 4 -rw-r--r--  1 user user  3647  5月  7 23:48 .bashrc
```

パターンに/bash/を指定します。現在の行は$0で参照します。

次に"bash"を含む行の6列を合計します。

```bash:console
$ ls -lsa | awk 'BEGIN {v=0} /bash/ {print $0; v+=$6} END {print v}'
32 -rw-------  1 user user 31623 10月  5 02:01 .bash_history
 4 -rw-r--r--  1 user user   220  5月  7 23:48 .bash_logout
 4 -rw-r--r--  1 user user  3647  5月  7 23:48 .bashrc
35490
```

BEGINでvを初期化します。v+=$6で6列を加算します。ENDでvを表示します。

フィールドの区切りのデフォルトは空白です。

次のようにして区切り文字が変更できます。
カンマに変更する例です。

**-F オプション**で指定する例

```bash:console
$ cat data.txt 
taro,20,kanagawa
jiro,19,tokyo
saburo,16,chiba
$ cat data.txt | awk -F, '{ print $1 }' 
taro
jiro
saburo
```

**FS**で指定する例

```bash:console
$ cat data.txt | awk 'BEGIN { FS="," } { print $2 }' 
20
19
16
```

# print
printで標準出力に文字列を表示できます。
次に例を示します

```bash:console
$ awk 'BEGIN { print "a","b" }'
a b
$ awk 'BEGIN { print "a""b" }'
ab
$ awk 'BEGIN { print "a" "b" }'
ab
$ awk 'BEGIN { OFS=";"; print "a","b" }'
a;b
```

文字列を","で繋げると文字列の間にseparatorが出力されます。
separatorはOFS変数に値を設定することで変更できます。
OFSの初期値は空白" "です。

printfを使うとC Language の printfと類似の方法で表示できます。
改行コードを明示する必要があります。

```bash:console
$ ls -lsa $HOME | head | awk '{ printf "%x %d\n", NR, NR }'
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
a 10
```

# script file
awk programをファイルに保存してbash scriptのように利用できます。

```awk:test.awk
#!/usr/bin/awk -f

BEGIN {
    print "Hello"
}

{ print }
```

実行します。

```bash:console
$ ls -lsa $HOME | ./test.awk | head
Hello
合計 260
 4 drwxr-xr-x 22 user user  4096 10月 14 10:19 .
 4 drwxr-xr-x  3 root root  4096  5月  7 23:55 ..
28 -rw-------  1 user user 24850 10月 11 08:04 .ICEauthority
 4 -rw-------  1 user user   201 10月 11 08:04 .Xauthority
 4 drwx------  3 user user  4096  8月 28 22:08 .anthy
 4 -rw-r--r--  1 user user   177  5月  7 23:51 .apport-ignore.xml
36 -rw-------  1 user user 36578 10月 14 10:19 .bash_history
 4 -rw-r--r--  1 user user   220  5月  7 23:48 .bash_logout
```

# Built-in Variables
awkには定義済みの変数があります。
次に定義済みの変数一覧を示します。

| name | description |
|:--|:--|
| $0 | 現在処理中の行 |
| $1 | 現在の行に含まれる1番目のフィールド |
| $N | 現在の行に含まれるN番目のフィールド |
| FS  | Field Separator ; 入力のフィールド区切り |
| RS | Record Separator ; 入力の改行コード|
| OFS | Output Field Separator ; 出力のフィールド区切り |
| ORS | Output Record Separator ; 出力の改行コード|
| NR | Number of Records ; 行番号 |

## NR
NRはBEGINで0, 各行で行番号、ENDで行数になります。

```awk:test.awk
#!/usr/bin/awk -f

BEGIN {
    print "BEGIN NR", NR
}
{
    print NR
}

END {
    print "END NR", NR
}
```

```bash:console
$ ls -lsa $HOME | head | ./test.awk 
BEGIN NR 0
1
2
3
4
5
6
7
8
9
10
END NR 10
```

# 制御構造

```awk:test.awk
#!/usr/bin/awk -f

BEGIN {
    print "if"
    i = 10
    if (i == 10) {
        print "i == 10"
    } else if (i == 20) {
        print "i == 20"
    } else {
        print "else"
    }
    print

    print "for"
    for (i=0; i<3; i++) {
        print i
    }
    print
    
    print "while"
    i = 3
    while (i--) {
        print i
    }
    print
}
```

```bash:console
$ ./test.awk 
if
i == 10

for
0
1
2

while
2
1
0

```

# Array
行をまたがった処理をする場合、読み込んだ行をArrayに保存しておきます。
Arrayの使い方を説明します。

```awk:test.awk
#!/usr/bin/awk -f

{
    a[NR] = $0
}

END {
    for (i=1; i<=NR; i++) {
        print a[i]
    }
}
```

NRは最初の行で1となります。最後の行で行数となります。
よって**for文は1から始まってNで終わっていることに注意してください。**

```bash:console
$ ls -lsa $HOME | head | ./test.awk 
合計 260
 4 drwxr-xr-x 22 user user  4096 10月 14 10:19 .
 4 drwxr-xr-x  3 root root  4096  5月  7 23:55 ..
28 -rw-------  1 user user 24850 10月 11 08:04 .ICEauthority
 4 -rw-------  1 user user   201 10月 11 08:04 .Xauthority
 4 drwx------  3 user user  4096  8月 28 22:08 .anthy
 4 -rw-r--r--  1 user user   177  5月  7 23:51 .apport-ignore.xml
36 -rw-------  1 user user 36578 10月 14 10:19 .bash_history
 4 -rw-r--r--  1 user user   220  5月  7 23:48 .bash_logout
 4 -rw-r--r--  1 user user  3647  5月  7 23:48 .bashrc
```

配列のサイズは**length**で取得できます。

```bash:console
$ ls -lsa | head | awk '{ a[NR] = $0 } END { print length(a) }'
10
```

# パターンマッチ
~(tidle)と//を使うことで文字列が正規表現にマッチしたかどうかを確認できます。

```bash:console
$ ls -lsa | head
合計 264
 4 drwxr-xr-x 22 user user  4096 10月 14 22:37 .
 4 drwxr-xr-x  3 root root  4096  5月  7 23:55 ..
28 -rw-------  1 user user 25232 10月 14 22:30 .ICEauthority
 4 -rw-------  1 user user   201 10月 14 22:30 .Xauthority
 4 drwx------  3 user user  4096  8月 28 22:08 .anthy
 4 -rw-r--r--  1 user user   177  5月  7 23:51 .apport-ignore.xml
36 -rw-------  1 user user 36578 10月 14 10:19 .bash_history
 4 -rw-r--r--  1 user user   220  5月  7 23:48 .bash_logout
 4 -rw-r--r--  1 user user  3647  5月  7 23:48 .bashrc
$ ls -lsa | head | awk '{ if($0~/23/) print }'
 4 drwxr-xr-x  3 root root  4096  5月  7 23:55 ..
28 -rw-------  1 user user 25232 10月 14 22:30 .ICEauthority
 4 -rw-r--r--  1 user user   177  5月  7 23:51 .apport-ignore.xml
 4 -rw-r--r--  1 user user   220  5月  7 23:48 .bash_logout
 4 -rw-r--r--  1 user user  3647  5月  7 23:48 .bashrc
$ ls -lsa | head | awk '{ if($0~/root/) print }'
 4 drwxr-xr-x  3 root root  4096  5月  7 23:55 ..
```

Arrayを使ってパターンマッチした行とその次の行を表示する例です。

```awk:test.awk
#!/usr/bin/awk -f

{
    a[NR] = $0
}

END {
    for (i=1; i<=NR; i++) {
        if (a[i]~/bss|data/) {
            print a[i]
            print a[i+1]
        }
    }
}
```

実行ファイルからrodata / data / bss sectionの情報を取り出します。

```bash:console
$ readelf -S ./a.out | ./test.awk 
  [15] .rodata           PROGBITS         0000000000400580  00000580
       0000000000000004  0000000000000004  AM       0     0     4
  [24] .data             PROGBITS         0000000000601028  00001028
       0000000000000010  0000000000000000  WA       0     0     8
  [25] .bss              NOBITS           0000000000601038  00001038
       0000000000000008  0000000000000000  WA       0     0     1
```

<!--

```awk:test.awk
```
```bash:console
```

-->