【Grep】ripgrep / git grep を使用した文字列検索

# はじめに
影響範囲調査時などで活躍するため、備忘録として記載しています。
同じように調査を行われる方にも、この記事が参考になれば幸いです。

# Todo
Git管理されているリポジトリにおいて、対象の文字列が使われているファイルのパス・内容を出力する

## 検索ワード例
例）AccessTokenモデルが使用されていそうなワードを予測
- `AccessToken`
- `access_token`
- `at.`

## 出力したい内容

- ファイルパス
- 特定のワードが使用されている行の内容
  - 行番号の表示も行う
- 下記ファイルは検索対象から除外したい
  - `***_spec.rb`
  - `schema.rb`

# ripgrep

:::note
事前にインストールしておく
```terminal
brew install ripgrep
```
:::

### テンプレート

```rb:テンプレ1
rg '検索ワード1|検索ワード2' -g '!検索から除外したいファイル名' -n --sort-files
```

```rb:テンプレ2（ファイルパスがメイン）
rg '検索ワード1|検索ワード2' -g '!検索から除外したいファイル名' -c --sort path
```

### ファイルパス・行番号・その行の内容を表示するパターン

```rb:コマンド例1
rg 'AccessToken|access_token|at\.' -g '!*spec.rb' -g '!schema.rb' -n --sort-files
```

```rb:出力結果1
app/controllers/books_search_controller.rb
14:    return redirect_to root_path unless current_user&.has_valid_access_token?

app/controllers/homes_controller.rb
7:      if current_user&.has_valid_access_token?

app/controllers/sessions_controller.rb
15:      save_access_token(user, auth_hash) unless user.has_valid_access_token?
38:  def save_access_token(user, auth_hash)
39:    access_token = auth_hash['credentials']['token']
40:    AccessToken.create_access_token_for_user!(user, access_token)

app/models/access_token.rb
1:class AccessToken < ApplicationRecord
9:    def create_access_token_for_user!(user, token)

app/models/user.rb
2:  has_many :access_tokens
11:  def has_valid_access_token?
12:    access_tokens.where('expires_at > ?', Time.zone.now).exists?

app/services/chat_gpt_service.rb
15:    @client = OpenAI::Client.new(access_token: ENV.fetch("OPENAI_ACCESS_TOKEN"))

app/views/books_search/index.html.erb
23:    const hasValidAccessToken = '<%= current_user&.has_valid_access_token? %>';
28:    if (!hasValidAccessToken) {

config/initializers/openai.rb
2:  config.access_token = ENV.fetch("OPENAI_ACCESS_TOKEN")

db/migrate/20240220141834_create_access_tokens.rb
1:class CreateAccessTokens < ActiveRecord::Migration[7.0]
3:    create_table :access_tokens do |t|

test/models/access_token_test.rb
3:class AccessTokenTest < ActiveSupport::TestCase
```

### ファイルパス、および検索ワードの出現回数を表示するパターン

```rb:コマンド例2
rg 'AccessToken|access_token|at\.' -g '!*spec.rb' -g '!schema.rb' -c --sort path
```

```rb:出力結果2
app/controllers/books_search_controller.rb:1
app/controllers/homes_controller.rb:1
app/controllers/sessions_controller.rb:4
app/models/access_token.rb:2
app/models/user.rb:3
app/services/chat_gpt_service.rb:1
app/views/books_search/index.html.erb:2
config/initializers/openai.rb:1
db/migrate/20240220141834_create_access_tokens.rb:2
test/models/access_token_test.rb:1
```


# git grepの場合

### テンプレート

```rb:テンプレ1
git grep -e 検索ワード1 -e 検索ワード2 -n ':!検索から除外したいファイル名'
```
```rb:テンプレ2（ファイルパスがメイン）
git grep -e 検索ワード1 -e 検索ワード2 --count ':!検索から除外したいファイル名'
```

### ファイルパス・行番号・その行の内容を表示するパターン

```rb:コマンド例1
git grep -e AccessToken -e access_token -e 'at\.' -n ':!*spec.rb' ':!*schema.rb'
```

```rb:出力結果1
app/controllers/books_search_controller.rb:14:    return redirect_to root_path unless current_user&.has_valid_access_token?
app/controllers/homes_controller.rb:7:      if current_user&.has_valid_access_token?
app/controllers/sessions_controller.rb:15:      save_access_token(user, auth_hash) unless user.has_valid_access_token?
app/controllers/sessions_controller.rb:38:  def save_access_token(user, auth_hash)
app/controllers/sessions_controller.rb:39:    access_token = auth_hash['credentials']['token']
app/controllers/sessions_controller.rb:40:    AccessToken.create_access_token_for_user!(user, access_token)
app/models/access_token.rb:1:class AccessToken < ApplicationRecord
app/models/access_token.rb:9:    def create_access_token_for_user!(user, token)
app/models/user.rb:2:  has_many :access_tokens
app/models/user.rb:11:  def has_valid_access_token?
app/models/user.rb:12:    access_tokens.where('expires_at > ?', Time.zone.now).exists?
app/services/chat_gpt_service.rb:15:    @client = OpenAI::Client.new(access_token: ENV.fetch("OPENAI_ACCESS_TOKEN"))
app/views/books_search/index.html.erb:23:    const hasValidAccessToken = '<%= current_user&.has_valid_access_token? %>';
app/views/books_search/index.html.erb:28:    if (!hasValidAccessToken) {
config/initializers/openai.rb:2:  config.access_token = ENV.fetch("OPENAI_ACCESS_TOKEN")
db/migrate/20240220141834_create_access_tokens.rb:1:class CreateAccessTokens < ActiveRecord::Migration[7.0]
db/migrate/20240220141834_create_access_tokens.rb:3:    create_table :access_tokens do |t|
test/models/access_token_test.rb:3:class AccessTokenTest < ActiveSupport::TestCase
```

### ファイルパス、および検索ワードの出現回数を表示するパターン

```rb:コマンド例2
git grep -e AccessToken -e access_token -e 'at\.' --count ':!*spec.rb' ':!*schema.rb'
```

```rb:出力結果2
app/controllers/books_search_controller.rb:1
app/controllers/homes_controller.rb:1
app/controllers/sessions_controller.rb:4
app/models/access_token.rb:2
app/models/user.rb:3
app/services/chat_gpt_service.rb:1
app/views/books_search/index.html.erb:2
config/initializers/openai.rb:1
db/migrate/20240220141834_create_access_tokens.rb:2
test/models/access_token_test.rb:1
```


# 実行結果をファイルに出力したい場合
```terminal
上記いずれかのコマンド > ~/Desktop/result.txt
```
:::note
コマンドの末尾に、`> ~/Desktop/result.txt`を追記するだけ。
:::

:::note
コマンド実行後、Desktopに`result.txt`が作成され、内容も出力される。
:::

## ripgrep

```rb:コマンド例
rg 'AccessToken|access_token|at\.' -g '!*spec.rb' -g '!schema.rb' -n --sort-files > ~/Desktop/result.txt
```

```rb:result.rb
app/controllers/books_search_controller.rb:14:    return redirect_to root_path unless current_user&.has_valid_access_token?
app/controllers/homes_controller.rb:7:      if current_user&.has_valid_access_token?
app/controllers/sessions_controller.rb:15:      save_access_token(user, auth_hash) unless user.has_valid_access_token?
app/controllers/sessions_controller.rb:38:  def save_access_token(user, auth_hash)
app/controllers/sessions_controller.rb:39:    access_token = auth_hash['credentials']['token']
app/controllers/sessions_controller.rb:40:    AccessToken.create_access_token_for_user!(user, access_token)
app/models/access_token.rb:1:class AccessToken < ApplicationRecord
app/models/access_token.rb:9:    def create_access_token_for_user!(user, token)
app/models/user.rb:2:  has_many :access_tokens
app/models/user.rb:11:  def has_valid_access_token?
app/models/user.rb:12:    access_tokens.where('expires_at > ?', Time.zone.now).exists?
app/services/chat_gpt_service.rb:15:    @client = OpenAI::Client.new(access_token: ENV.fetch("OPENAI_ACCESS_TOKEN"))
app/views/books_search/index.html.erb:23:    const hasValidAccessToken = '<%= current_user&.has_valid_access_token? %>';
app/views/books_search/index.html.erb:28:    if (!hasValidAccessToken) {
config/initializers/openai.rb:2:  config.access_token = ENV.fetch("OPENAI_ACCESS_TOKEN")
db/migrate/20240220141834_create_access_tokens.rb:1:class CreateAccessTokens < ActiveRecord::Migration[7.0]
db/migrate/20240220141834_create_access_tokens.rb:3:    create_table :access_tokens do |t|
test/models/access_token_test.rb:3:class AccessTokenTest < ActiveSupport::TestCase
```

## git grep

```rb:コマンド例
git grep -e AccessToken -e access_token -e 'at\.' -n ':!*spec.rb' ':!*schema.rb' > ~/Desktop/result.txt
```

```rb:result.rb
app/controllers/books_search_controller.rb:14:    return redirect_to root_path unless current_user&.has_valid_access_token?
app/controllers/homes_controller.rb:7:      if current_user&.has_valid_access_token?
app/controllers/sessions_controller.rb:15:      save_access_token(user, auth_hash) unless user.has_valid_access_token?
app/controllers/sessions_controller.rb:38:  def save_access_token(user, auth_hash)
app/controllers/sessions_controller.rb:39:    access_token = auth_hash['credentials']['token']
app/controllers/sessions_controller.rb:40:    AccessToken.create_access_token_for_user!(user, access_token)
app/models/access_token.rb:1:class AccessToken < ApplicationRecord
app/models/access_token.rb:9:    def create_access_token_for_user!(user, token)
app/models/user.rb:2:  has_many :access_tokens
app/models/user.rb:11:  def has_valid_access_token?
app/models/user.rb:12:    access_tokens.where('expires_at > ?', Time.zone.now).exists?
app/services/chat_gpt_service.rb:15:    @client = OpenAI::Client.new(access_token: ENV.fetch("OPENAI_ACCESS_TOKEN"))
app/views/books_search/index.html.erb:23:    const hasValidAccessToken = '<%= current_user&.has_valid_access_token? %>';
app/views/books_search/index.html.erb:28:    if (!hasValidAccessToken) {
config/initializers/openai.rb:2:  config.access_token = ENV.fetch("OPENAI_ACCESS_TOKEN")
db/migrate/20240220141834_create_access_tokens.rb:1:class CreateAccessTokens < ActiveRecord::Migration[7.0]
db/migrate/20240220141834_create_access_tokens.rb:3:    create_table :access_tokens do |t|
test/models/access_token_test.rb:3:class AccessTokenTest < ActiveSupport::TestCase
```

:::note
ファイルに出力する場合は、`ripgrep`、`git grep`ともに変わらない（同じ結果にしたい場合、`ripgrep`は明示的にsortさせる必要がある）
:::



# 最後に
個人的には、検索ワードが複数ある場合 `ripgrep` が使いやすいなと思ってます。