コマンドラインだけでアクセスログをいい感じに解析する

# 対象読者
* "アクセスログからアクセス傾向を調査して"って言われて、どこから手をつけていいかわからない人
* コマンドラインに少し恐怖心がある人
* python　などのスクリプトを組んで解析するのがめんどくさくなってきた人

などなど、CLI の操作に慣れたいモチベはあるんだけど、ちょっとよくわからないなぁっていう初心者の方に向けて、解説できたらいいなと思います。

この記事のコマンドは、実際に私が現場での調査でよく使う組み合わせであるので、使えたら実際の現場での調査に役に立つと思うので、ぜひ理解して習得してもらえたら嬉しいです！

# この記事の目標
下記の対象データにあるようなアクセスログをコマンドラインで一発で解析すること。
具体的には
>1. コンテキストパスごとのアクセス数を取得して、アクセスの多い順に並べる
>1. ユーザごとのアクセス数を、多い順に並べる

ができるようになることを目指します。

# この記事で覚える必要があること
* `awk` + `print` の組み合わせ
* `awk` の `-F` オプション
* `awk` + `sort` + `uniq` の組み合わせ

# 対象データ
file name: tmp_accesslog
contents:
```
54.36.149.41 - - [22/Jan/2019:03:56:14 +0330] "GET /filter/27|13%20%D9%85%DA%AF%D8%A7%D9%BE%DB%8C%DA%A9%D8%B3%D9%84,27|%DA%A9%D9%85%D8%AA%D8%B1%20%D8%A7%D8%B2%205%20%D9%85%DA%AF%D8%A7%D9%BE%DB%8C%DA%A9%D8%B3%D9%84,p53 HTTP/1.1" 200 30577 "-" "Mozilla/5.0 (compatible; AhrefsBot/6.1; +http://ahrefs.com/robot/)" "-"
31.56.96.51 - - [22/Jan/2019:03:56:16 +0330] "GET /image/60844/productModel/200x200 HTTP/1.1" 200 5667 "https://www.zanbil.ir/m/filter/b113" "Mozilla/5.0 (Linux; Android 6.0; ALE-L21 Build/HuaweiALE-L21) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.158 Mobile Safari/537.36" "-"
31.56.96.51 - - [22/Jan/2019:03:56:16 +0330] "GET /image/61474/productModel/200x200 HTTP/1.1" 200 5379 "https://www.zanbil.ir/m/filter/b113" "Mozilla/5.0 (Linux; Android 6.0; ALE-L21 Build/HuaweiALE-L21) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.158 Mobile Safari/537.36" "-"
40.77.167.129 - - [22/Jan/2019:03:56:17 +0330] "GET /image/14925/productModel/100x100 HTTP/1.1" 200 1696 "-" "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)" "-"
91.99.72.15 - - [22/Jan/2019:03:56:17 +0330] "GET /product/31893/62100/%D8%B3%D8%B4%D9%88%D8%A7%D8%B1-%D8%AE%D8%A7%D9%86%DA%AF%DB%8C-%D9%BE%D8%B1%D9%86%D8%B3%D9%84%DB%8C-%D9%85%D8%AF%D9%84-PR257AT HTTP/1.1" 200 41483 "-" "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:16.0)Gecko/16.0 Firefox/16.0" "-"
40.77.167.129 - - [22/Jan/2019:03:56:17 +0330] "GET /image/23488/productModel/150x150 HTTP/1.1" 200 2654 "-" "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)" "-"
40.77.167.129 - - [22/Jan/2019:03:56:18 +0330] "GET /image/45437/productModel/150x150 HTTP/1.1" 200 3688 "-" "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)" "-"
40.77.167.129 - - [22/Jan/2019:03:56:18 +0330] "GET /image/576/article/100x100 HTTP/1.1" 200 14776 "-" "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)" "-"
66.249.66.194 - - [22/Jan/2019:03:56:18 +0330] "GET /filter/b41,b665,c150%7C%D8%A8%D8%AE%D8%A7%D8%B1%D9%BE%D8%B2,p56 HTTP/1.1" 200 34277 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" "-"
40.77.167.129 - - [22/Jan/2019:03:56:18 +0330] "GET /image/57710/productModel/100x100 HTTP/1.1" 200 1695 "-" "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)" "-"
引用元: https://www.kaggle.com/datasets/eliasdabbas/web-server-access-logs
```


# step1: `awk` + `print` を覚える
まずは、一つ目の目標である下記を目指します。
> コンテキストパスごとのアクセス数を取得して、アクセスの多い順に並べる

このような状況では、`awk` を使用することが非常に強力です。
文字列の操作を得意とするプログラミング言語であり、ファイルから特定の文字列を抽出したり計算したりに優れています。

ただ、`awk` は一つのプログラミング言語であるので、正直できることがたくさんありすぎて、初心者の方は混乱しますし、心が折れます。

そのため、まずは覚えることを必要最低限に絞って、
1. `awk` の基本的な型を覚える
1. `print` を組み合わせる

の二つを覚えましょう。

### `awk` の基本的な型を覚える
基本的な型は、
```bash
awk '{コマンド}' [入力ファイル]
```
となります。

もしくは、パイプで標準入力に渡して

```
cat '入力ファイル' | awk '{コマンド}'
```

という形でも良いです。実際の現場では後者の形がよく現れるので、後者を覚えておくと良いです。

いずれにしろ、"標準入力に何か文字列を渡す + それに対するコマンドを指定する" というセットになります。

このようにすることで、`awk` は "空白区切りで文字列を分割したもの" を対象コマンドに引き渡すことができます。

### `print` を組み合わせる
上記までで、"空白区切の文字列" を取得することができました。
あとは、これを表示するのみで、それには `print` がやりやすいです。

`awk` によって区切った文字列は、`$1`、 `$2` のように "ドルマーク + 数字" で左から順番にアクセスできます。
以上を踏まえた上で、今回のテストデータにコマンドを適用して、コンテキストパスに絡んだ文字列を取得しようとすると、以下のようになります。

```bash
$ cat tmp_accesslog | awk '{print $7}'
/filter/27|13%20%D9%85%DA%AF%D8%A7%D9%BE%DB%8C%DA%A9%D8%B3%D9%84,27|%DA%A9%D9%85%D8%AA%D8%B1%20%D8%A7%D8%B2%205%20%D9%85%DA%AF%D8%A7%D9%BE%DB%8C%DA%A9%D8%B3%D9%84,p53
/image/60844/productModel/200x200
/image/61474/productModel/200x200
/image/14925/productModel/100x100
/product/31893/62100/%D8%B3%D8%B4%D9%88%D8%A7%D8%B1-%D8%AE%D8%A7%D9%86%DA%AF%DB%8C-%D9%BE%D8%B1%D9%86%D8%B3%D9%84%DB%8C-%D9%85%D8%AF%D9%84-PR257AT
/image/23488/productModel/150x150
/image/45437/productModel/150x150
/image/576/article/100x100
/filter/b41,b665,c150%7C%D8%A8%D8%AE%D8%A7%D8%B1%D9%BE%D8%B2,p56
/image/57710/productModel/100x100
```

ここまでで、おおよそ下準備は整いました。
ここからは、
* コンテキストパスだけを抽出する
* 重複をまとめる
* 並び替える

を行えば目的が完了します。


# step2: `awk` で特定文字で、文字列の分割を行う
step2 では、下記を行うために、特定の文字を使って、対象文字列を分割する方法を覚えます。
> コンテキストパスだけを抽出する

こちらは簡単で、`-F` オプションを使用すれば OK です。

**文法**
```
awk -F "[指定したい文字列]" '{コマンド}' [入力ファイル]
```

今回の例では、コンテキストパスだけを取得したいので、"/" で区切ってみます。
以下がコマンドの結果です。

```bash
$ cat tmp_accesslog | awk -F "/" '{print "/"$4}'
/filter
/image
/image
/image
/product
/image
/image
/image
/filter
/image
```

これで、残す課題は、
* 重複をまとめる
* 並び替える

の二つになりました。

# step3: `awk` + `sort` + `uniq` の強さを知る
最後は、`awk` を飛び越えて、`sort` と `uniq` との組み合わせを知ることが必要となります。
それぞれのコマンドは、

* `sort`: テキストファイル or 標準入力を行単位で並べ替える
* `uniq`: 適切に並び替えられたテキストファイル or 標準入力の重複を削除する

ということができます。
また、`uniq` コマンドには、`-c` という削除した重複がいくつあったかをカウントするオプションがあるので、これらを組み合わせることで、目標だった "コンテキストパスごとのアクセス数を取得して、アクセスの多い順に並べる" を達成できます。

コマンドを適用した結果が以下です。

```bash
$ cat tmp_accesslog | awk -F "/" '{print "/"$4}' | sort | uniq -c
   2 /filter
   7 /image
   1 /product
```

これで、一目でどのコンテキストパスが、どれだけアクセスきているのかがわかるようになりました。

最後に、アクセスの多い順に並べ替える一手間を加えます。
`sort` には `-n` と `-r` というオプションがあり、それぞれ
* `-n`: 数値として扱う
* `-r`: 並び順を逆転させる

ということができます。

これを組み合わせてできた最終形態のコマンドが以下の通りです。
```bash
$ cat tmp_accesslog | awk -F "/" '{print "/"$4}' | sort | uniq -c | sort -nr
   7 /image
   2 /filter
   1 /product
```

これで、本当に１番目の目標が達成できました！

また、ここまでの知識を応用することで、２番目の "ユーザごとのアクセス数を、多い順に並べる" は下記のようになります。
```bash
$ cat tmp_accesslog | awk -F " " '{print $1}' | sort | uniq -c | sort -nr
   5 40.77.167.129
   2 31.56.96.51
   1 91.99.72.15
   1 66.249.66.194
   1 54.36.149.41
```


# おわりに
如何だったでしょうか？
下記の `awk` についての知識を身に付けるだけで、無事に目標を達成できたと思います！

* `awk` + `print` の組み合わせ
* `awk` の `-F` オプション
* `awk` + `sort` + `uniq` の組み合わせ

近年では、CLI 操作をしなくて済むような環境が増えてきましたが、いざというときに、さっとコマンドラインだけで色々な解析ができるようになっておくとエンジニアとしての地力がつくと思うので、ぜひトライしてみてください！