grepコマンドを体系的に学ぶ

## はじめに
最近実務でgrepコマンドを使う機会が何度かあったのですが、全然使いこなせていないと感じることが多かったので、これを機に体系的に学び直してみました。

## 対象読者
- grepコマンドについて知らない、使ったことがない方
- 業務でたまにgrepコマンドを使うが、その都度オプションなどを調べている方
- grepコマンドを体系的に学んだことがない方


## grepコマンドとは

指定した文字列やパターンに一致する行をディレクトリやファイルから検索するコマンドです。
様々なオプションが用意されていたり正規表現が使えることから、柔軟な検索が可能です。

## grepの基本操作
以下が基本文法になります。
```
$ grep [オプション] 文字列 <検索対象のファイル or ディレクトリ>
```

指定できるオプション一覧
| オプション | 説明 |
| ---- | ---- |
|-r|ディレクトリとその中にあるディレクトリから検索|
|-v|特定の文字列を除外し検索|
|-E|拡張正規表現を使い検索|
|-l|検索結果にファイル名のみ出力|
|-c|検索で一致した行数を出力|
|-h|検索結果にファイル名を出力しない|
|-i|大文字・小文字区別せずに検索|
|-x|完全一致する行を検索|
|-n|検索結果に行番号を出力|
|-o|検索に一致した文字列のみを出力|
|-w|指定した単語に完全一致する行を検索|
|-C|検索結果に一致した行とその前後の行を指定した数だけ表示|
|-A|検索結果に一致した行とその後の行を指定した数だけ出力|
|-B|検索結果に一致した行とその前の行を指定した数だけ出力|
|-L|検索に該当しなかったファイルを出力|
|--exclude="*.拡張子"|特定の拡張子のファイルを検索から除外|
|--exclude-dir=path|特定のディレクトリを検索から除外|
|--include="*.拡張子"|特定の拡張子のファイルをのみを検索対象とする|

オプションは以下のように複数組み合わせても使えます。

```
$ grep -nE "user_(path|url)"

# -Eで拡張正規表現を使い検索
# -nで検索結果に行数を出力する


$ grep -rohE "[ぁ-んァ-ン亜-熙　-】]+" ./*

# -rディレクトリの中のディレクトリも検索対象とする
# -oと-hで検索に一致した文字列のみを表示（ファイル名は出力しない）
# -Eで拡張正規表現を使って検索
```

## grepコマンドの使用例

これまで紹介したオプションなどの情報を元に、どのような使用例があるのかを例付きで記載していきます。

（※例にはRailsのコードを使用しております🙏）

### 指定したファイルから特定の文字を検索

**例）ログファイルから`ERROR`を含む行を検索**
```
$ grep "ERROR" development.log
```

### 指定したディレクトリから特定の文字を検索

**例）usersディレクトリ内のファイルから`name`を含む行を検索**
```
$ grep "name" users/*
```

### -rでディレクトリとその中にあるディレクトリから検索

ディレクトリの中にあるディレクトリも検索対象に含めたい場合は、`-r`オプションをつける必要があります。

**例）controllersディレクトリとその中にあるディレクトリから`User.create`を含む行を検索**
```
$ grep -r "User\.create" app/controllers/*
```

### AND検索
パイプ（`|`）でgrepを繋げてAND検索が行えます。

**例）controllersディレクトリから`user`と`profile`を含む行を検索**
```
$ grep -r "user" app/controllers/* | grep profile
```

後述する`-v`オプションと組み合わせることで「ooを含むがooは含まない」といった検索も可能です。

**例）ログファイルから`status`を含むかつ`200`が含まれていない行を検索**
```
$ grep "status" development.log | grep -v 200
```

### OR検索
-eオプションを使うことOR検索が行えます

**例）ログファイルから`INSERT`または`UPDATE`を含む行を検索**
```
$ grep -e "INSERT" -e "UPDATE" development.log
```

### -vで特定の文字列を除外し検索
**例）ログファイルから`INFO`を含まない行を検索**
```
$ grep -v "INFO" development.log
```

### -Eで拡張正規表現を使い検索
:::note info
拡張正規表現とは基本の正規表現を拡張したものです。
たとえば以下の記法は拡張正規表現に該当します。
- `+`：直前の文字の１回以上の繰り返し
- `?`：直前の文字の０回もしくは１回の繰り返し
- `a|b`：左右いずれかの文字列

[参考](https://marusuke-blog.com/linux-expanded-regular-expression/)
:::

**例）左右いずれかの文字列に該当する行を検索**
```
$ grep -E "user_(path|url)"
```

**例）oo（1文字以上のアルファベット）_pathという文字列に該当する行を検索**
```
$ grep -E "[a-zA-Z]+_path"
```

### -lで検索結果にファイル名のみ出力

特定の文字列を含む行があるファイル名のリストを作りたい場合など有効です。

**例）-rと組み合わせてcontrollersディレクトリとその中にあるディレクトリからApplicationControllerクラスを継承しているControllerクラスのファイル名を出力**
```
$ grep -rl "< ApplicationController" app/controllers/*

users_controller.rb
posts_controller.rb
```

### -cで検索で一致した行数を出力
**例）modelsディレクトリから`User.create`を含む行の数をファイルごとに出力する**
```
$ grep -c "User\.create" app/models/*

app/models/user.rb:1
app/models/post.rb:2
```

### -hで検索結果にファイル名を出力しない
該当する行だけを表示したい場合は有効です。

**例）controllersディレクトリから`create`を含む行を検索し、行のみを出力**
```
$ grep -h "create" app/controllers/*

before_action :create_posts, only: [:create, :update]
def create
  User.create(post_id: @post.id)
def create_posts
  Post.create
def create_profile
  user.create_profile
```

### -iで大文字・小文字区別せず検索
**例）ログファイルから大文字小文字区別せず`error`を含む行を検索**
```
$ grep -i "error" development.log
```

### -xで完全一致する行を検索
例えば`user`と`admin_user`の2つが存在し、`user`のみを検索したい場合などに有効です。

**例）modelsディレクトリから`user.update(name: name)`に完全一致する行を検索**
```
$ grep -x "user\.update(name: name)" app/models/*
```

### -nで検索結果に行番号を出力する
**例）modelsディレクトリから`User.create`を含む行を検索し行番号付きで出力**
```
$ grep -n "User\.create" app/models/*

app/controllers/users_controller.rb:2 before_action :create_posts, only: [:create, :update]
app/controllers/users_controller.rb:11 def create
app/controllers/users_controller.rb:12   User.create(post_id: @post.id)
app/controllers/users_controller.rb:37 def create_posts
app/controllers/users_controller.rb:38   Post.create
user.rb:20 def create_profile
user.rb:21   user.create_profile
```

### -oで検索に一致した文字列のみを出力
正規表現を使って絞り込んだ結果、どのような文字列が該当するのかを確認したい場合に有効です。


1行の中に複数一致する場合、それぞれ一致する文字列を別行で表示します。
```
$ echo "apple banana apple" | grep -o "apple"

apple
apple
```

**例）ログファイルからどのテーブルに対してSELECT文が発行されているかを出力**
```
$ grep -o "SELECT * FROM [a-zA-Z]" development.log

development.log:SELECT * FROM users;
development.log:SELECT * FROM posts;
```

### -wで指定した単語に完全一致する行を検索
単語として完全一致する行を検索します。
単語の前後が英数字やアンダースコアの場合はマッチしません。
```
# 以下はマッチする
$ echo "User" | grep -w "User"
$ echo "User\.create" | grep -w "User"

# 以下はマッチしない
$ echo "Users" | grep -w "User"
$ echo "User1" | grep -w "User"
$ echo "User_" | grep -w "User"
```

**例）`user`という単語に一致する行だけを検索する（`user_oo`や`oo_user`などは除外したい）**
```
grep -w "user" app/models/*
```

### -Cで検索結果に一致した行とその前後の行を指定した数だけ出力

検索行の前後でどのような処理が呼ばれているかを確認したい場合に有効です。

**例）user.rbファイルから、Profile.createを含む行を検索し、前後5行を出力**
```
$ grep -C 5 "Profile\.create" user.rb
```

### -Aで検索結果に一致した行とその後の行を指定した数だけ出力
検索行の後にどのような処理が呼ばれているかを確認したい場合に有効です。

**例）ログファイルで`SELECT * FROM users;`を含む行を検索し、後の5行を出力**
```
$ grep -A 5 "SELECT * FROM users;" development.log
```

### -Bで検索結果に一致した行とその前の行を指定した数だけ出力
検索行の前でどのような処理が呼ばれているかを確認したい場合に有効です。

**例）ログファイルで`ERROR`を含む行を検索し、前の10行を出力**
```
$ grep -B 10 "ERROR" development.log
```

### -Lで検索に該当しなかったファイルを出力

特定の処理を定義できていないファイルを探したい場合など有効です。

**例）controllersディレクトリとその中にあるディレクトリからApplicationControllerクラスを継承していないControllerクラスのファイルを出力**
```
$ grep -rL "< ApplicationController" app/controllers/*
```

### 特定の拡張子のファイルをのみを検索対象とする
`--include="*.<対象としたい拡張子>"`とすれば指定した拡張子のファイルだけを検索します。

**例）appディレクトリとその中にあるディレクトリかつ、拡張子が`.rb`のファイルからUserを含む行を検索**
```
$ grep -r "User" app/* --include="*.rb"
```

### 特定のディレクトリを検索から除外
`--exclude-dir=<除外するディレクトリのパス>`とすれば指定したディレクトリ以外で検索します。

**例）appディレクトリからviewsディレクトリを除外した上でUserを含む行を検索**
```
$ grep -r "User" app/* --exclude-dir=views
```

### 特定の拡張子のファイルを検索から除外

`--exclude="*.<除外する拡張子>"`とすれば指定したファイル以外で検索します。

**例）appディレクトリから拡張子が`.html`のファイルを除外した上でUserを含む行を検索**
```
$ grep -r "User" app/* --exclude="*.html"
```

## おまけ

### 正規表現を活用した例

これまで紹介したオプションだけでなく、正規表現を使うことでより柔軟な検索が可能です。
以下で正規表現を使用した検索例をいくつか紹介します。


**例１）先頭に`user_`が付いているメソッドを検索**
```
grep -r "def user_.*" app/*
```

**例２）末尾に`_user`がついているメソッドを検索**
```
grep -rE "def.*_user$" app/*
```

**例３）メールアドレスらしき文字列を検索**
```
grep -E '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-z]{2,}'
```

**例４）空白文字で始まる行を検索**
```
grep "^[[:space:]]" template.txt
```


## 参考にさせていただいた記事・ドキュメント
https://www.ibm.com/docs/ja/aix/7.1?topic=g-grep-command#grep__row-d3e144158

https://linuxcommand.net/grep/#--exclude

https://zenn.dev/captain_blue/articles/grep-useful-pattern

https://eng-entrance.com/linux-command-grep

https://qiita.com/yagiryu/items/6128ad998560510be5de

https://qiita.com/YASU_ojisan/items/8225c87acb4e1468dc90