Try / ripgrep in Y minutes

ripgrep is a command line tool that searches your files for patterns that you give it. It's similar to grep, but provides a better user experience and is (generally) faster.

Basics · Recursive search · Useful options · Replacements · Configuration · Further reading

✨ This is an open source guide. Feel free to improve it!

Basics

ripgrep behaves as if it were reading each file line by line:

  • If a line matches the pattern given to ripgrep, that line is printed.
  • If a line does not match the pattern, the line is not printed.

The best way to see how this works is with an example.

We'll try searching httpurr source code, which I have already downloaded to the /opt/httpurr directory like this:

cd /opt
curl -OL https://github.com/rednafi/httpurr/archive/refs/tags/v0.1.2.tar.gz
tar xvzf v0.1.2.tar.gz
mv httpurr-0.1.2 httpurr
cd httpurr

Search in file · Partial matches · Regular expressions · Fixed strings · Multiple patterns

Search in file

Let's find all occurrences of the word codes in README.md:

rg codes README.md
3:    <strong><i> >> HTTP status codes on speed dial << </i></strong>
30:* List the HTTP status codes:
54:* Filter the status codes by categories:
124:		  Print HTTP status codes by category with --list;
131:		  Print HTTP status codes

So what happened here? ripgrep read the contents of README.md, and for each line that contained codes, ripgrep printed it to the terminal.

ripgrep includes the line number for each line by default (use -n/--line-number to force this) and highlights the matches (use --color=always to force this).

Partial matches

ripgrep supports partial matches by default:

rg descr README.md
81:* Display the description of a status code:
127:		  Print the description of an HTTP status code

The word description matches the descr search pattern.

To search for whole words instead, use the -w (--word-regexp) option:

rg --word-regexp code README.md
81:* Display the description of a status code:
84:	httpurr --code 410
94:	The HyperText Transfer Protocol (HTTP) 410 Gone client error response code
99:	code should be used instead.
126:	  -c, --code [status code]
127:		  Print the description of an HTTP status code

ripgrep found strings containing the word code, but not codes. Try removing --word-regexp and see how the results change.

Regular expressions

By default, ripgrep treats the search pattern as a regular expression. Let's find all lines with a word that contains res followed by other letters:

rg 'res\w+' README.md
94:	The HyperText Transfer Protocol (HTTP) 410 Gone client error response code
95:	indicates that access to the target resource is no longer available at the
152:of the rest.

\w+ means "one or more word-like characters" (e.g. letters like p or o, but not punctuation like . or !), so response, resource, and rest all match.

Suppose we are only interested in 4 letter words starting with res:

rg 'res\w\b' README.md
152:of the rest.

\b means "word boundary" (e.g. a space, a punctuation character, or the end of a line), so rest matches, but response and resource don't.

Finally, let's search for 3-digit numbers (showing first 10 matches with head):

rg '\d\d\d' README.md | head
45:	100    Continue
46:	101    Switching Protocols
47:	102    Processing
48:	103    Early Hints
69:	200    OK
70:	201    Created
71:	202    Accepted
72:	203    Non-Authoritative Information
73:	204    No Content
74:	205    Reset Content

A full tutorial on regular expressions is beyond the scope of this guide, but ripgrep's specific syntax is documented in the regex package.

Fixed strings

What if we want to search for a literal string instead of a regular expression? Suppose we are interested in a word code followed by a dot:

rg 'code.' src/data.go | head
8:The HTTP 100 Continue informational status response code indicates that
14:status code in response before sending the body.
31:The HTTP 101 Switching Protocols response code indicates a protocol to which the
53:Deprecated: This status code is deprecated. When used, clients may still accept
56:The HTTP 102 Processing informational status response code indicates to client
59:This status code is only sent if the server expects the request to take
112:The HTTP 200 OK success status response code indicates that the request has
141:The HTTP 201 Created success status response code indicates that the request has
149:The common use case of this status code is as the result of a POST request.
165:The HTTP 202 Accepted response status code indicates that the request has been

Since . means "any character" in regular expressions, our pattern also matches code , codes and other cases we are not interested in.

To treat the pattern as a literal string, use the -F (--fixed-strings) option:

rg --fixed-strings 'code.' src/data.go
197:to responses with any status code.
283:Browsers accessing web pages will never encounter this status code.
695:to an error code.
1027:erroneous cases it happens, they will handle it as a generic 400 status code.
1051:Regular web servers will normally not return this status code. But some other
1418:then the server responds with the 510 status code.

Much better!

Multiple patterns

To search for multiple patterns, list them with the -e (--regexp) option. ripgrep will output lines matching at least one of the specified patterns.

For example, search for make or run:

rg -e 'make' -e 'run' README.md
139:* Go to the root directory and run:
141:	make init
145:	make lint
149:	make test

If you have many patterns, it may be easier to put them in a file and point ripgrep to it with -f (--file):

echo 'install' > /tmp/patterns.txt
echo 'make' >> /tmp/patterns.txt
echo 'run' >> /tmp/patterns.txt

rg --file=/tmp/patterns.txt README.md
13:* On MacOS, brew install:
17:	    && brew install httpurr
20:* Or elsewhere, go install:
23:	go install github.com/rednafi/httpurr/cmd/httpurr
139:* Go to the root directory and run:
141:	make init
145:	make lint
149:	make test

Previously, we used ripgrep to search a single file, but ripgrep is perfectly capable of recursively searching in directories.

Search in directory · Automatic filtering · File globs · File types

Search in directory

Let's find all unexported functions (they start with a lowercase letter):

rg 'func [a-z]\w+' .
./cmd/httpurr/main.go:12:func main() {
./src/cli.go:16:func formatStatusText(text string) string {
./src/cli.go:21:func printHeader(w *tabwriter.Writer) {
./src/cli.go:35:func printStatusCodes(w *tabwriter.Writer, category string) error {
./src/cli.go:105:func printStatusText(w *tabwriter.Writer, code string) error {

This search returned matches from both the cmd and src directories. If you are only interested in cmd, specify it instead of .:

rg 'func [a-z]\w+' cmd
cmd/httpurr/main.go:12:func main() {

To search multiple directories, list them all like this:

rg 'func [a-z]\w+' cmd src
cmd/httpurr/main.go:12:func main() {
src/cli.go:16:func formatStatusText(text string) string {
src/cli.go:21:func printHeader(w *tabwriter.Writer) {
src/cli.go:35:func printStatusCodes(w *tabwriter.Writer, category string) error {
src/cli.go:105:func printStatusText(w *tabwriter.Writer, code string) error {

Automatic filtering

ripgrep is smart enough to ignore some paths when searching:

  • patterns from .gitignore,
  • hidden files and directories,
  • binary files,
  • symlinks.

For example, let's search for GitHub action jobs:

rg 'jobs:' .
exit status 1

Since the GitHub stuff is in a hidden .github directory, ripgrep won't find it. But it will with the -. (--hidden) option:

rg --hidden 'jobs:' .
./.github/workflows/automerge.yml:8:jobs:
./.github/workflows/lint.yml:11:jobs:
./.github/workflows/release.yml:10:jobs:
./.github/workflows/test.yml:11:jobs:

Similarly, there are options to enable other paths:

  • --no-ignore to search patterns from .gitignore;
  • -a (--text) to search binary files;
  • -L (--follow) to follow symlinks.

ripgrep allows you to override the ignored paths from .gitignore with .ignore files. See the official guide for details.

File globs

Let's search for httpurr:

rg --max-count=5 httpurr .
./README.md:2:    <h1>ᗢ httpurr</h1>
./README.md:16:	brew tap rednafi/httpurr https://github.com/rednafi/httpurr \
./README.md:17:	    && brew install httpurr
./README.md:23:	go install github.com/rednafi/httpurr/cmd/httpurr
./README.md:33:	httpurr --list
./cmd/httpurr/main.go:4:	"github.com/rednafi/httpurr/src"
./go.mod:1:module github.com/rednafi/httpurr
./httpurr.rb:7:  homepage "https://github.com/rednafi/httpurr"
./httpurr.rb:12:      url "https://github.com/rednafi/httpurr/releases/download/v0.1.1/httpurr_Darwin_x86_64.tar.gz"
./httpurr.rb:16:        bin.install "httpurr"
./httpurr.rb:20:      url "https://github.com/rednafi/httpurr/releases/download/v0.1.1/httpurr_Darwin_arm64.tar.gz"
./httpurr.rb:24:        bin.install "httpurr"
./src/cli.go:24:	fmt.Fprintf(w, "\nᗢ httpurr\n")
./src/cli_test.go:64:	want := "\nᗢ httpurr\n==========\n\n"

Note that I have limited the number of results per file to 5 with the -m (--max-count) option to keep the results readable in case there are many matches.

Quite a lot of results. Let's narrow it down by searching only in .go files:

rg -g '*.go' httpurr .
./cmd/httpurr/main.go:4:	"github.com/rednafi/httpurr/src"
./src/cli.go:24:	fmt.Fprintf(w, "\nᗢ httpurr\n")
./src/cli_test.go:64:	want := "\nᗢ httpurr\n==========\n\n"

The -g (--glob) option takes a glob (filename pattern), typically containing a fixed part (.go in our example) and a wildcard * ("anything but the path separator").

Another example — search in files named http-something:

rg -g 'http*' httpurr .
./httpurr.rb:7:  homepage "https://github.com/rednafi/httpurr"
./httpurr.rb:12:      url "https://github.com/rednafi/httpurr/releases/download/v0.1.1/httpurr_Darwin_x86_64.tar.gz"
./httpurr.rb:16:        bin.install "httpurr"
./httpurr.rb:20:      url "https://github.com/rednafi/httpurr/releases/download/v0.1.1/httpurr_Darwin_arm64.tar.gz"
./httpurr.rb:24:        bin.install "httpurr"
./httpurr.rb:31:      url "https://github.com/rednafi/httpurr/releases/download/v0.1.1/httpurr_Linux_arm64.tar.gz"
./httpurr.rb:35:        bin.install "httpurr"
./httpurr.rb:39:      url "https://github.com/rednafi/httpurr/releases/download/v0.1.1/httpurr_Linux_x86_64.tar.gz"
./httpurr.rb:43:        bin.install "httpurr"

To negate the glob, prefix it with !. For example, search everywhere except the .go, .md, and .rb files:

rg -g '!*.{go,md,rb}' httpurr .
./go.mod:1:module github.com/rednafi/httpurr

All that is left is go.mod.

To apply multiple filters, specify multiple glob options. For example, find all functions except those in test files:

rg -g '*.go' -g '!*_test.go' 'func ' .
./cmd/httpurr/main.go:12:func main() {
./src/cli.go:16:func formatStatusText(text string) string {
./src/cli.go:21:func printHeader(w *tabwriter.Writer) {
./src/cli.go:35:func printStatusCodes(w *tabwriter.Writer, category string) error {
./src/cli.go:105:func printStatusText(w *tabwriter.Writer, code string) error {
./src/cli.go:123:func Cli(w *tabwriter.Writer, version string, exitFunc func(int)) {

File types

Instead of using a glob to filter by extension, you can use ripgrep's support for file types. Let's search for httpurr in .go files:

rg -t go httpurr .
./cmd/httpurr/main.go:4:	"github.com/rednafi/httpurr/src"
./src/cli.go:24:	fmt.Fprintf(w, "\nᗢ httpurr\n")
./src/cli_test.go:64:	want := "\nᗢ httpurr\n==========\n\n"

The -t (--type) option restricts the search results to files of a certain type (Go source files in our example).

To exclude files of a certain type, use -T (--not-type):

rg -T go -T md -T ruby httpurr .
./go.mod:1:module github.com/rednafi/httpurr

We've excluded Go, Markdown and Ruby files, so all that's left is go.mod (personally, I'd consider it a Go file as well, but ripgrep disagrees).

Useful options

ripgrep supports a number of additional search and output options you may find handy.

Ignore case · Inverse matching · Count matches · Show matches only · Show files only · Show context · Multiline search

Ignore case

Remember our search for codes in the README?

rg codes README.md
3:    <strong><i> >> HTTP status codes on speed dial << </i></strong>
30:* List the HTTP status codes:
54:* Filter the status codes by categories:
124:		  Print HTTP status codes by category with --list;
131:		  Print HTTP status codes

It returns codes matches, but not Codes because ripgrep is case-sensitive by default. To change this, use -i (--ignore-case):

rg --ignore-case codes README.md
3:    <strong><i> >> HTTP status codes on speed dial << </i></strong>
30:* List the HTTP status codes:
40:	Status Codes
54:* Filter the status codes by categories:
64:	Status Codes
124:		  Print HTTP status codes by category with --list;
131:		  Print HTTP status codes

There is also -S (--smart-case), which behaves like --ignore-case unless the search pattern is all caps:

rg --smart-case HTTP README.md
3:    <strong><i> >> HTTP status codes on speed dial << </i></strong>
30:* List the HTTP status codes:
94:	The HyperText Transfer Protocol (HTTP) 410 Gone client error response code
109:	https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410
124:		  Print HTTP status codes by category with --list;
127:		  Print the description of an HTTP status code
131:		  Print HTTP status codes

Searching for HTTP matches HTTP, but not https or httpurr.

Inverse matching

To find lines that do not contain the pattern, use -v (--invert-match). For example, find the non-empty lines without the @ symbol:

rg --invert-match -e '@' -e '^$' Makefile
1:.PHONY: lint
2:lint:
8:.PHONY: lint-check
9:lint-check:
14:.PHONY: test
15:test:
20:.PHONY: clean
21:clean:
27:.PHONY: init
28:init:

Count matches

To count the number of matched lines (per file), use -c (--count). For example, count the number of functions in each .go file:

rg --count -t go 'func ' .
./cmd/httpurr/main.go:1
./src/cli.go:5
./src/cli_test.go:10
./src/data_test.go:2

Note that --count counts the number of lines, not the number of matches. For example, there are 6 words string in src/cli.go, but two of them are on the same line, so --count reports 5:

rg -w --count 'string' src/cli.go
5

To count the number of matches instead, use --count-matches:

rg -w --count-matches 'string' src/cli.go
6

Show matches only

By default, ripgrep prints the entire line containing the match. To show only the matching part, use -o (--only-matching).

Suppose we want to find functions named print-something:

rg --only-matching -t go 'func print\w+' .
./src/cli.go:21:func printHeader
./src/cli.go:35:func printStatusCodes
./src/cli.go:105:func printStatusText

The results are much cleaner than without --only-matching (try removing the option in the above command and see for yourself).

Show files only

If there are too many matches, you may prefer to show only the files where the matches occurred. Use -l (--files-with-matches) to do this:

rg --files-with-matches 'httpurr' .
./README.md
./cmd/httpurr/main.go
./go.mod
./httpurr.rb
./src/cli.go
./src/cli_test.go

Show context

Remember when we searched for GitHub jobs?

rg 'jobs:' .github/workflows
.github/workflows/automerge.yml:8:jobs:
.github/workflows/lint.yml:11:jobs:
.github/workflows/release.yml:10:jobs:
.github/workflows/test.yml:11:jobs:

These results are kind of useless, because they don't return the actual job name (which is on the next line after jobs). To fix this, let's use -C (--context), which shows N lines around each match:

rg --context=1 'jobs:' .github/workflows
.github/workflows/automerge.yml-7-
.github/workflows/automerge.yml:8:jobs:
.github/workflows/automerge.yml-9-  dependabot:
--
.github/workflows/lint.yml-10-
.github/workflows/lint.yml:11:jobs:
.github/workflows/lint.yml-12-  golangci:
--
.github/workflows/release.yml-9-
.github/workflows/release.yml:10:jobs:
.github/workflows/release.yml-11-  goreleaser:
--
.github/workflows/test.yml-10-
.github/workflows/test.yml:11:jobs:
.github/workflows/test.yml-12-  test:

It might be even better to show only the next line after the match, since we are not interested in the previous one. Use -A (--after-context) for this:

rg --after-context=1 'jobs:' .github/workflows
.github/workflows/automerge.yml:8:jobs:
.github/workflows/automerge.yml-9-  dependabot:
--
.github/workflows/lint.yml:11:jobs:
.github/workflows/lint.yml-12-  golangci:
--
.github/workflows/release.yml:10:jobs:
.github/workflows/release.yml-11-  goreleaser:
--
.github/workflows/test.yml:11:jobs:
.github/workflows/test.yml-12-  test:

Nice!

I have another idea for searching GitHub jobs. Since the job name is always on the next line after the literal jobs:, let's enable multiline searching with -U (--multiline):

rg --multiline 'jobs:\n\s+\w+' .github/workflows
.github/workflows/automerge.yml:8:jobs:
.github/workflows/automerge.yml:9:  dependabot:
.github/workflows/lint.yml:11:jobs:
.github/workflows/lint.yml:12:  golangci:
.github/workflows/release.yml:10:jobs:
.github/workflows/release.yml:11:  goreleaser:
.github/workflows/test.yml:11:jobs:
.github/workflows/test.yml:12:  test:

Now we can see the job names even without using --context.

Replacements

ripgrep provides a limited ability to replace matched text with some other text.

Replace matches · Replace entire line · Replace groups

Replace matches

Remember our search for codes in the README?

rg codes README.md
3:    <strong><i> >> HTTP status codes on speed dial << </i></strong>
30:* List the HTTP status codes:
54:* Filter the status codes by categories:
124:		  Print HTTP status codes by category with --list;
131:		  Print HTTP status codes

Now let's replace all codes with ids using the -r (--replace) option:

rg codes -r ids README.md
3:    <strong><i> >> HTTP status ids on speed dial << </i></strong>
30:* List the HTTP status ids:
54:* Filter the status ids by categories:
124:		  Print HTTP status ids by category with --list;
131:		  Print HTTP status ids

Replace entire line

Replace applies only to the matching portion of text. To replace an entire line of text, include the entire line in the match like this:

rg '^.*codes.*$' -r 'REDACTED' README.md
3:REDACTED
30:REDACTED
54:REDACTED
124:REDACTED
131:REDACTED

Alternatively, you can combine the -o (--only-matching) option with --replace to achieve the same result:

rg codes -or 'REDACTED' README.md
3:REDACTED
30:REDACTED
54:REDACTED
124:REDACTED
131:REDACTED

Replace groups

Replacements can include capturing groups. Let's say we want to find all occurrences of status followed by another word and join them together with a dash. The pattern we might use is status\s+(\w+):

  • literal string status,
  • followed by any number of whitespace characters,
  • followed by any number of "word" characters (e.g. letters).

We put the \w+ in a "capturing group" (indicated by parentheses) so that we can reference it later in our replacement string. For example:

rg 'status\s+(\w+)' -r 'status-$1' README.md
3:    <strong><i> >> HTTP status-codes on speed dial << </i></strong>
30:* List the HTTP status-codes:
54:* Filter the status-codes by categories:
81:* Display the description of a status-code:
124:		  Print HTTP status-codes by category with --list;
126:	  -c, --code [status-code]
127:		  Print the description of an HTTP status-code
131:		  Print HTTP status-codes

Our replacement string (status-$1) consists of the literal status- followed by the contents of the capturing group at index 1.

Capturing groups actually start at index 0, but the 0th capturing group always corresponds to the entire match. The capturing group at index 1 always corresponds to the first explicit capturing group found in the regex pattern.

ripgrep will never modify your files. The --replace flag only controls ripgrep's output. (And there is no flag to let you do a replacement in a file.)

Configuration

ripgrep's has reasonable defaults, but you can change them with configuration files.

ripgrep does not automatically look for a config file in a predefined directory. To use a config file, set the RIPGREP_CONFIG_PATH environment variable to its path.

Here's an example of a configuration file:

cat /tmp/.ripgreprc
# Trim really long lines and show a preview
--max-columns=40
--max-columns-preview

# Search hidden files / directories (e.g. dotfiles)
--hidden

# Do not search git files
--glob
!.git/*

# Ignore case unless all caps
--smart-case

When specifying a flag that has a value, either put the flag and the value on the same line with a = sign (e.g. --max-columns=40), or put the flag and the value on two different lines (e.g., --glob). Do not put them on the same line without an equal sign (e.g. not --max-columns 40).

Let's search using this config:

export RIPGREP_CONFIG_PATH=/tmp/.ripgreprc
rg httpurr . | head
./.goreleaser.yml:14:    main: ./cmd/httpurr
./.goreleaser.yml:45:  - name: httpurr
./.goreleaser.yml:46:    homepage: "https://github.com/rednaf [... omitted end of long line]
./.goreleaser.yml:50:      name: httpurr
./README.md:2:    <h1>ᗢ httpurr</h1>
./README.md:16:	brew tap rednafi/httpurr https://github [... omitted end of long line]
./README.md:17:	    && brew install httpurr
./README.md:23:	go install github.com/rednafi/httpurr/c [... omitted end of long line]
./README.md:33:	httpurr --list
./README.md:37:	ᗢ httpurr

Further reading

ripgrep supports more features, such as explicit handling of encodings or searching binary data. See the official guide for details.

Use rg --help to see all supported options (we have covered less than half of them in this guide).

Andrew Gallant + 1 others · original · CC-BY-SA-4.0 · 2024-03-19