新世代 grep の使い方 ripgrep(rg) と silver-searcher(ag)

はやいは正義

ripgrep が現在進行形で大正義らしい。名前からなんとなく「闇を切り裂く grep」みたいなイメージが浮かぶ。こう、アスファルトタイヤを切り裂きながら暗闇走り抜ける素敵なサムシング、を感じる... のはおぢさんだけ、か。 github.com

なぜ、rip という言葉が頭に浮かんだのかは覚えていない。何か "速い" とか "テキストを切り裂く" みたいなイメージが浮かんだんだ。rip が "Rest in Peace"、(魂よ)安らかに眠れ (つまり ripgrep によって grep の役目を終わらせるってこと) の略語たりうるってことに、その時はまだ気づいていなかった。

I don't remember how it popped into my head, but "rip" came up as something that meant "fast," as in, "to rip through your text." The fact that RIP is also an initialism for "Rest in Peace" (as in, "ripgrep kills grep") never really dawned on me. (FAQ.md)

インストールもはやくするためにワンライナを書いておく。バージョン 0.8.0 で鋭意開発中なので今年中にあと何回か叩くことになる気がする。

$ curl -sfSL https://github.com/BurntSushi/ripgrep/releases/download/0.8.0/ripgrep-0.8.0-x86_64-unknown-linux-musl.tar.gz | sudo tar xzf - -C /usr/local/bin/ --wildcards */rg --strip=1

所感

ag を踏襲しつつ速度をさらにはやめた感じ。新世代 grep 共通で ag も rg も結果表示が以下の形式になるのは同じ。ただこれはターミナルの表示上だけで行指向な本質はそのまま持っている。パイプで繋げるとマッチ行だけになる。デフォルトだとドット(.) ではじまるファイルは検索対象外となるが --hidden を使えば含めてくれる。

ファイル名
マッチ行

ag/rg 使い方逆引き一覧

やりたいこと ag (v2.1.0) rg (v0.8.0)
インストール(mac) $ brew install the_silver_searcher $ brew install ripgrep
インストール(ubuntu) $ sudo apt-get install silversearcher-ag 上記参照
サポートしているファイルタイプ一覧を出す $ ag --list-file-types $ rg --type-list
yml のファイルのリストを出す $ ag --yaml -l . $ rg -t yaml --files
yml のファイルで Deployment が含まれるファイルのリストを出す $ ag --yaml Deployment -l $ rg -t yaml Deployment -l
scala のファイルで case class を検索する $ ag --scala '^case class' $ rg -t scala '^case class'
scala のファイルで getOrElse を使っている(良い)箇所を検索する $ ag --scala -w getOrElse もしくは $ ag --scala '\bgetOrElse\b' $ rg -t scala -w getOrElse もしくは $ rg -t scala '\bgetOrElse\b'
scala のファイルで get を使っている(悪い)箇所を検索する $ ag --scala -w get $ rg -t scala -w get
scala のファイルで get を使っている箇所が全ファイル中どれくらいなのかの統計情報を出す $ ag --scala -w get --stats --stats-only なし
scala のファイルで get.wild を使っているワイルドな箇所を検索する(パイプつなぎのファイル名つき/行番号つき) $ ag --scala --filename --numbers -w get | ag wild $ rg -t scala --with-filename --line-number -w get | rg wild
色付き表示のまま less につなげる $ ag --scala get --pager 'less -R' $ rg -p -t scala get | less -R

ripgrep には統計の機能がないようだが、そこまで使わないし、作者がやる気になればすぐ作れるだろう。速度は rg のほうが早く感じたが両者とも (grep に感じる) ストレスを感じさせない速度だったので(てきとう)使いたいほうを使えばいいと思う。

複数行マッチをサポートする ag とサポートしない rg

例えば Kubernetes の yaml ファイルで以下のようなファイルがあるとする。ag は複数行マッチに対応しており --multiline オプションがデフォルトで有効になっているため ag --yaml 'name.*\n.*spec.*\n.*replicas' . でマッチできる。一方 rg は実装の複雑さとパフォーマンスへの影響から複数行マッチには対応していないし、今後もおそらく対応する予定がない。rg の作者の Andrew Gallant はこの機能差分は ag と rg の違いを示すいい例だとコメントしている。(^_^;

(... snip ...)
  name: user-ranking-app
spec:
  replicas: 10

github.com

検索結果からさらに検索したい時はパイプ/プロセス置換

例えば linux カーネルソースから cgroup_selvictim の両方を含んでいる行を検索したい時(例はてきとう)。パイプのほうがプロセス置換よりも若干速い。フィルタする時は最初の検索にファイル名表示 -H と行番号表示 -n をつけておくほうが便利。

$ time rg -nH -t c cgroup_sel | rg victim
mm/memcontrol.c:1285:int mem_cgroup_select_victim_node(struct mem_cgroup *memcg)
mm/memcontrol.c:1305:int mem_cgroup_select_victim_node(struct mem_cgroup *memcg)
mm/vmscan.c:3124:   nid = mem_cgroup_select_victim_node(memcg);
include/linux/memcontrol.h:428:int mem_cgroup_select_victim_node(struct mem_cgroup *memcg);

real  0m42.889s
user  0m1.455s
sys   0m4.252s
$ time rg victim <(rg -nH -t c cgroup_sel)
1:mm/memcontrol.c:1285:int mem_cgroup_select_victim_node(struct mem_cgroup *memcg)
2:mm/memcontrol.c:1305:int mem_cgroup_select_victim_node(struct mem_cgroup *memcg)
3:mm/vmscan.c:3124: nid = mem_cgroup_select_victim_node(memcg);
4:include/linux/memcontrol.h:428:int mem_cgroup_select_victim_node(struct mem_cgroup *memcg);

real  0m48.226s
user  0m1.958s
sys   0m6.691s

percol 連携に都合のいいオプション

alias s='ag --all-types --hidden --noheading --nonumbers --nofilename . ~/memo/*| percol'

grep は滅びぬ、なんでも蘇るさ

新世代 grep は速度や表示の点で使いやすいけれども、それでも grep が滅びることはない。速度だけを見れば完敗だけれど。grep の問題は歴史的なしがらみだと思う。長年使われてきたせいで、さまざまなスクリプトが依存しており、仕様を変えると世界中に様々な影響が出てきてしまう。高速化もできないことはないが、後方互換性を維持する慎重な開発方針にならざるを得ない、といったところだろうか。あれ、人間も同じようなことが起きるような... |ω・)

d.hatena.ne.jp