grep をこんな風に使ってます話

この記事は、[大国魂（ITブログ） Advent Calendar 2019](https://adventar.org/calendars/3955) の8日目です。

# 記事概要

普段インフラ運用でどんな風に grep を利用しているか、という使用例を書き残しておこうと思います。
なお、登場するファイル名やログが古いのは今年の SECCON CTF 用VM が残っていたのでそれを使っているからです。
今年はボーナス問題含めて3問しか解けず、実質1問だけの正解となりました。かなしみ。

# grep の基本形
## テキストファイルを grep したい
grep と言えば通常は以下の形で利用すると思います。

```
grep <検索したい文字列> <ファイル名>
```

/var/log/messages 内の error という文字列が含まれる行を表示したい場合は以下となります。

```
grep error /var/log/messages
```

私の場合、grep はパイプで繋いでいくことが多いです。
この時、最初の grep で指定する <ファイル名> がその場所に書かれているのが気になってしまいます。

```
grep <検索したい文字列> <ファイル名> | grep <追加条件1> | grep <追加条件2>
```

grep 利用部分は SQL の where 句のように条件だけを記載するエリアにしたほうが(自分にとって)わかりやすいため、
以下のように cat で出力したファイルに対して利用しています。
これ以降の記載も大体この形になります。

```
cat /var/log/messages | grep error
```

# grep の利用目的

私が grep を利用する際の目的は、以下のような調査が主となります。

- 新規導入されるアプリケーションの事前ログ調査
- 監視していなかったがこれから監視したいログファイルを網羅的に事前調査
- アプリケーション側で何らかのアラートが上がったものの、原因がよくわからん際の調査
- その他、依頼ベースや自分が気になった部分のログ洗い出しのため。

この後、必要に応じてシェルスクリプトなり Zabbix なりで監視したりしなかったりします。

# grep でよく使うオプション
## i: 大文字小文字を区別しない

ここまでの例では導入のため記載していませんでしたが、私が grep を利用する際は i オプションを確実といっていいレベルで利用します。
具体的には以下です。

```
cat /var/log/messages | grep -i error
```

なぜなら、大文字小文字網羅的に見たいからです。
例えば error という文字列だけでも、大体以下の3パターンが想定されます。

```
error
Error
ERROR
```

i を付けないと上記一番上の小文字の error しか検知できず、一部のみしか拾えません。
さらに実例に近い形にすると、err で出力されるパターンもあるので以下での利用となります。

```
cat /var/log/messages | grep -i err
```

## e: 検索パターンの追加

複数条件を用いて検索したいときに利用します。
具体的には以下です。

```
cat /var/log/messages | grep -i -e err -e fatal -e fail -e warn
```

上記は大文字小文字を気にせず、err または fatal または fail または warn が含まれる行を抽出します。
利用目的にも記載した網羅的に見たい時のためのもので、このぐらいであれば大体危なさそうなメッセージは拾えるであろう、という位置づけとなります。

## v: 検索から除外するパターンの指定

表示したくない行に含まれるメッセージを指定して、検索から除外する v オプションです。
具体的には切り分け/調査が完了した行を検索から除外します。

```
cat /var/log/messages | grep -i -e err -e fatal -e fail -e warn | grep -v ACPI
```

上記は大文字小文字を気にせず、err または fatal または fail または warn が含まれる結果の中から ACPI が含まれる行を除外します。

vを付けない場合のサンプル実行結果は以下です。

```
[root@centos7 ~]# cat /var/log/messages | grep -i -e err -e fatal -e fail -e warn
Oct 20 10:40:25 centos7 kernel: ACPI: Using IOAPIC for interrupt routing
Oct 20 10:40:25 centos7 kernel: acpi PNP0A03:00: fail to add MMCONFIG information, can't access extended PCI configuration space under this bridge.
Oct 20 10:40:25 centos7 kernel: ACPI: PCI Interrupt Link [LNKA] (IRQs 5 9 10 *11)
Oct 20 10:40:25 centos7 kernel: ACPI: PCI Interrupt Link [LNKB] (IRQs 5 9 *10 11)
Oct 20 10:40:25 centos7 kernel: ACPI: PCI Interrupt Link [LNKC] (IRQs 5 *9 10 11)
Oct 20 10:40:25 centos7 kernel: ACPI: PCI Interrupt Link [LNKD] (IRQs 5 9 10 *11)
Oct 20 10:40:27 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Oct 20 10:40:27 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Oct 20 10:40:31 centos7 lvm: WARNING: lvmetad is being updated, retrying (setup) for 10 more seconds.
Oct 20 10:40:34 centos7 augenrules: failure 1
Oct 20 10:40:34 centos7 augenrules: failure 1
Oct 20 10:40:34 centos7 rngd: Failed to init entropy source 0: Hardware RNG Device
Oct 20 10:44:11 centos7 kernel: hrtimer: interrupt took 6235917 ns
Dec  8 08:06:30 centos7 kernel: ACPI: Using IOAPIC for interrupt routing
Dec  8 08:06:30 centos7 kernel: acpi PNP0A03:00: fail to add MMCONFIG information, can't access extended PCI configuration space under this bridge.
Dec  8 08:06:30 centos7 kernel: ACPI: PCI Interrupt Link [LNKA] (IRQs 5 9 10 *11)
Dec  8 08:06:30 centos7 kernel: ACPI: PCI Interrupt Link [LNKB] (IRQs 5 9 *10 11)
Dec  8 08:06:30 centos7 kernel: ACPI: PCI Interrupt Link [LNKC] (IRQs 5 *9 10 11)
Dec  8 08:06:30 centos7 kernel: ACPI: PCI Interrupt Link [LNKD] (IRQs 5 9 10 *11)
Dec  8 08:06:32 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Dec  8 08:06:32 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Dec  8 08:06:38 centos7 augenrules: failure 1
Dec  8 08:06:38 centos7 augenrules: failure 1
Dec  8 08:06:39 centos7 rngd: Failed to init entropy source 0: Hardware RNG Device
Dec  8 08:07:56 centos7 kernel: hrtimer: interrupt took 11247037 ns
```

v を付けて ACPI を指定すると以下となります。

```
[root@centos7 ~]# cat /var/log/messages-20191208 | grep -i -e err -e fatal -e fail -e warn | grep -v ACPI
Oct 20 10:40:25 centos7 kernel: acpi PNP0A03:00: fail to add MMCONFIG information, can't access extended PCI configuration space under this bridge.
Oct 20 10:40:27 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Oct 20 10:40:27 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Oct 20 10:40:31 centos7 lvm: WARNING: lvmetad is being updated, retrying (setup) for 10 more seconds.
Oct 20 10:40:34 centos7 augenrules: failure 1
Oct 20 10:40:34 centos7 augenrules: failure 1
Oct 20 10:40:34 centos7 rngd: Failed to init entropy source 0: Hardware RNG Device
Oct 20 10:44:11 centos7 kernel: hrtimer: interrupt took 6235917 ns
Dec  8 08:06:30 centos7 kernel: acpi PNP0A03:00: fail to add MMCONFIG information, can't access extended PCI configuration space under this bridge.
Dec  8 08:06:32 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Dec  8 08:06:32 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Dec  8 08:06:38 centos7 augenrules: failure 1
Dec  8 08:06:38 centos7 augenrules: failure 1
Dec  8 08:06:39 centos7 rngd: Failed to init entropy source 0: Hardware RNG Device
Dec  8 08:07:56 centos7 kernel: hrtimer: interrupt took 11247037 ns
```

ちなみに i も併用できます。一番上の行に表示されていた小文字の acpi も除外できているのがわかるかと思います。

```
[root@centos7 ~]# cat /var/log/messages-20191208 | grep -i -e err -e fatal -e fail -e warn | grep -i -v ACPI
Oct 20 10:40:27 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Oct 20 10:40:27 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Oct 20 10:40:31 centos7 lvm: WARNING: lvmetad is being updated, retrying (setup) for 10 more seconds.
Oct 20 10:40:34 centos7 augenrules: failure 1
Oct 20 10:40:34 centos7 augenrules: failure 1
Oct 20 10:40:34 centos7 rngd: Failed to init entropy source 0: Hardware RNG Device
Oct 20 10:44:11 centos7 kernel: hrtimer: interrupt took 6235917 ns
Dec  8 08:06:32 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Dec  8 08:06:32 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Dec  8 08:06:38 centos7 augenrules: failure 1
Dec  8 08:06:38 centos7 augenrules: failure 1
Dec  8 08:06:39 centos7 rngd: Failed to init entropy source 0: Hardware RNG Device
Dec  8 08:07:56 centos7 kernel: hrtimer: interrupt took 11247037 ns
```

また、e オプションと違って v オプションは並べて記載できないため、複数の除外が必要な場合はパイプで繋いでいきます。上記の結果からさらに augenrules が含まれる行を除外します。

```
[root@centos7 ~]# cat /var/log/messages-20191208 | grep -i -e err -e fatal -e fail -e warn | grep -i -v ACPI | grep -v augenrules
Oct 20 10:40:27 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Oct 20 10:40:27 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Oct 20 10:40:31 centos7 lvm: WARNING: lvmetad is being updated, retrying (setup) for 10 more seconds.
Oct 20 10:40:34 centos7 rngd: Failed to init entropy source 0: Hardware RNG Device
Oct 20 10:44:11 centos7 kernel: hrtimer: interrupt took 6235917 ns
Dec  8 08:06:32 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Dec  8 08:06:32 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Dec  8 08:06:39 centos7 rngd: Failed to init entropy source 0: Hardware RNG Device
Dec  8 08:07:56 centos7 kernel: hrtimer: interrupt took 11247037 ns
```

サンプルのため短くしてますが、実際に除外を入れる場合は確実にその行だけ除外という絞り込みをしたいため、もう少し長めの条件を指定しています。

除外したい結果たちを以下とします。
他の行の文字列の中に augenrules や ACPI が部分文字列として含まれているかもしれないので、前のスペースと後ろのコロンスペースまで含めて除外します。

```
Oct 20 10:40:34 centos7 augenrules: failure 1
Oct 20 10:40:34 centos7 augenrules: failure 1

Dec  8 08:06:30 centos7 kernel: ACPI: Using IOAPIC for interrupt routing
Dec  8 08:06:30 centos7 kernel: ACPI: PCI Interrupt Link [LNKA] (IRQs 5 9 10 *11)
Dec  8 08:06:30 centos7 kernel: ACPI: PCI Interrupt Link [LNKB] (IRQs 5 9 *10 11)
Dec  8 08:06:30 centos7 kernel: ACPI: PCI Interrupt Link [LNKC] (IRQs 5 *9 10 11)
Dec  8 08:06:30 centos7 kernel: ACPI: PCI Interrupt Link [LNKD] (IRQs 5 9 10 *11)
```

コマンドは以下の形となります。スペースやコロンは\でエスケープする必要があります。
小文字の acpi が含まれる行は、 `<space>acpi:<space>` ではないので以下の検索結果に含まれています。

```
[root@centos7 ~]# cat /var/log/messages-20191208 | grep -i -e err -e fatal -e fail -e warn | grep -i -v \ ACPI\:\  | grep -v \ 
augenrules\:\ 
Oct 20 10:40:25 centos7 kernel: acpi PNP0A03:00: fail to add MMCONFIG information, can't access extended PCI configuration space under this bridge.
Oct 20 10:40:27 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Oct 20 10:40:27 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Oct 20 10:40:31 centos7 lvm: WARNING: lvmetad is being updated, retrying (setup) for 10 more seconds.
Oct 20 10:40:34 centos7 rngd: Failed to init entropy source 0: Hardware RNG Device
Oct 20 10:44:11 centos7 kernel: hrtimer: interrupt took 6235917 ns
Dec  8 08:06:30 centos7 kernel: acpi PNP0A03:00: fail to add MMCONFIG information, can't access extended PCI configuration space under this bridge.
Dec  8 08:06:32 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Dec  8 08:06:32 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
Dec  8 08:06:39 centos7 rngd: Failed to init entropy source 0: Hardware RNG Device
Dec  8 08:07:56 centos7 kernel: hrtimer: interrupt took 11247037 ns
```

# おまけ


## zip/gzip で圧縮されたファイルを grep したい
zip/gzip で圧縮されたファイルを実ファイルに展開しなくても、以下で中身だけ標準出力に出力できます。
「このファイル圧縮されてるけど中身見たい、でも展開して別ファイル作るのもなぁ」というときに。

```
unzip -p /var/log/messages-20191020.zip | grep -i error
gunzip -dc /var/log/messages-20191020.gz | grep -i error
```


## r: 指定した文字列が存在するファイルを、指定したディレクトリ配下で再帰的に検索する

まれによく使うのが r オプションです。
指定したディレクトリ配下を指定した文字列で再帰的に検索してくれます。
主に「特定ディレクトリ配下に存在する、ある文字列が含まれているファイルを探す」時に使用します。
具体的にはスクリプト等で定義されている変数が、どのファイルで使用されているかの調査になります。
以下は /var/log/ ディレクトリ配下を大文字小文字問わない error という文字列で検索した結果です。
(具体的には、というのとかみ合ってないのはご容赦ください)

```
[root@centos7 ~]# grep -ir error /var/log/
/var/log/grubby_prune_debug:[1571462710] Error       : Could not find a bootloader configuration to back up
/var/log/anaconda/syslog:05:18:02,383 INFO dracut-pre-udev:modprobe: ERROR: could not insert 'floppy': No such device
(snip)
/var/log/dmesg.old:[    3.861424] [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
/var/log/dmesg:[    3.975740] [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
(snip)
/var/log/messages-20191208:Dec  8 08:06:32 centos7 kernel: [drm:vmw_host_log [vmwgfx]] *ERROR* Failed to send host log message.
```

私はほとんど使いませんが、n オプション(行番号表示)を併用すると、より見やすくなるのではないかと思います。

# 最後に
これらの grep を用いたり用いなかったりして調査が終わった後は、例えば監視チームに依頼するのであれば grep -E で必要な行だけ抽出する正規表現を作成したりしますが、正規表現なのでまた別の話とします。
調査目的であれば上記で十分かなぁと思い、これまで色々試してみましたが結局この記事の内容に落ち着きました。
あくまで参考なので、みなさまそれぞれのベストな形でオプションを組み合わせて grep されると良いのではないかなと思います。
私の場合は、SQL っぽく理解できると楽だなぁということで上記の形となっています。
e が OR、| が AND みたいな。

次回はまた来週、本当は今週書こうと思っていた内容を書く予定です。