# How ripgrep's Replacement Feature Works: A Deep Dive into --replace

> Explore ripgrep's --replace flag for efficient display-only text substitution and capture group interpolation. Learn how ripgrep enhances search without altering files.

- Repository: [Andrew Gallant/ripgrep](https://github.com/BurntSushi/ripgrep)
- Tags: deep-dive
- Published: 2026-03-05

---

**ripgrep's `--replace` flag performs display-only text substitution during search, interpolating capture groups like `$1` and `${name}` into the output without ever modifying source files.**

The `BurntSushi/ripgrep` repository provides a blazingly fast search tool that includes a powerful **ripgrep replacement** feature via the `-r` or `--replace` flag. Unlike traditional sed-style replacements, this feature is strictly display-only—it transforms how matches appear in your terminal without altering the underlying files. Understanding how this substitution pipeline works reveals why ripgrep can perform complex text transformations while maintaining its characteristic speed.

## How ripgrep Replacement Works Under the Hood

The implementation follows a three-stage pipeline that parses the replacement template, prepares efficient buffers, and executes the substitution for every matching line.

### Step 1: Parsing the --replace Flag

When you invoke `-r` or `--replace`, the flag definition in [`crates/core/flags/defs.rs`](https://github.com/BurntSushi/ripgrep/blob/main/crates/core/flags/defs.rs) handles parsing. The `Replace` struct stores the replacement template as a `BString` (byte string) in `LowArgs::replace`, preserving raw bytes to support searching non-UTF-8 data.

### Step 2: Initializing the Replacer

During result printing, the printer lazily creates a `Replacer<M>` struct defined in [`crates/printer/src/util.rs`](https://github.com/BurntSushi/ripgrep/blob/main/crates/printer/src/util.rs). This structure holds a reusable `Space<M>` allocation that stores:
- Capture group locations from the regex matcher
- A destination buffer for the interpolated output
- The new match offsets after replacement

By reusing these allocations across lines, ripgrep avoids per-line memory allocation overhead.

### Step 3: Executing the Substitution

For every line output—whether a match or a context line—the sink invokes `StandardSink::replace` in [`crates/printer/src/standard.rs`](https://github.com/BurntSushi/ripgrep/blob/main/crates/printer/src/standard.rs). If replacement is enabled, this calls `Replacer::replace_all` from [`crates/printer/src/util.rs`](https://github.com/BurntSushi/ripgrep/blob/main/crates/printer/src/util.rs), which:

1. Detects whether the search pattern is multiline
2. Strips line terminators for single-line searches to ensure look-around assertions work correctly
3. Invokes `replace_with_captures_in_context` to interpolate `$1`, `${foo}`, and `$$` using the matcher's capture data
4. Stores the transformed bytes in `Space::dst` and records the new match ranges for highlighting

## ripgrep Replacement Syntax and Capture Groups

The replacement string supports several interpolation patterns that reference captured portions of the match.

**Numeric capture groups** use `$n` syntax where `n` is the index of the capturing parenthesis (counting from the left-most opening parenthesis). `$0` expands to the entire match.

**Named capture groups** use `$name` syntax. If the name contains characters outside of letters, digits, or underscores, or if you need to disambiguate from surrounding text, use the braced form `${name}`.

**Escaping dollar signs** requires doubling them: `$$` produces a literal `$` character in the output.

When writing these patterns in shell commands, use single quotes to prevent the shell from interpreting `$1` as a variable. For example, write `rg --replace '$1' 'pattern'` rather than using double quotes.

## Practical Examples of ripgrep --replace

### Simple String Replacement

Replace occurrences of "error" with "warning" in the `src/` directory:

```bash
rg --replace 'warning' 'error' src/

```

### Swapping Capture Groups

Rearrange CSV fields by swapping the first and second columns:

```bash
rg --replace '$2,$1' '^([^,]+),([^,]+)' data.csv

```

This transforms "first,second" into "second,first".

### Named Capture Groups

Reformat ISO dates to European format using named groups:

```bash
rg --replace '${day}/${month}/${year}' \
   '(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})' logs.txt

```

### Literal Dollar Signs

Insert a literal dollar sign before prices:

```bash
rg --replace '$$${0}' '\d+' prices.txt

```

This converts "100" to "$100". The `$$` becomes a single `$`, and `${0}` inserts the matched number.

### Deleting Lines

Remove lines containing "TODO" by replacing the entire line with an empty string:

```bash
rg --replace '' '^.*TODO.*$' source.rs

```

## Key Source Files for ripgrep Replacement

The implementation spans the core flags and printer crates:

| File | Purpose |
|------|---------|
| [`crates/core/flags/defs.rs`](https://github.com/BurntSushi/ripgrep/blob/main/crates/core/flags/defs.rs) | Defines the `Replace` flag and parses the replacement template into `LowArgs::replace` as a `BString`. |
| [`crates/printer/src/util.rs`](https://github.com/BurntSushi/ripgrep/blob/main/crates/printer/src/util.rs) | Contains the `Replacer<M>` struct, `Space<M>` allocation management, and the `replace_all` method that performs capture interpolation. |
| [`crates/printer/src/standard.rs`](https://github.com/BurntSushi/ripgrep/blob/main/crates/printer/src/standard.rs) | Implements `StandardSink::replace`, which invokes the replacer for every match and context line during output formatting. |
| [`tests/feature.rs`](https://github.com/BurntSushi/ripgrep/blob/main/tests/feature.rs) | Integration tests verifying replacement behavior, including capture group handling and edge cases. |

## Summary

- **Display-only operation**: ripgrep's `--replace` flag transforms output for terminal display and never modifies source files on disk.
- **Three-stage pipeline**: The feature parses the replacement template in [`defs.rs`](https://github.com/BurntSushi/ripgrep/blob/main/defs.rs), initializes a reusable `Replacer<M>` struct from [`util.rs`](https://github.com/BurntSushi/ripgrep/blob/main/util.rs), and executes substitutions via `StandardSink::replace` in [`standard.rs`](https://github.com/BurntSushi/ripgrep/blob/main/standard.rs).
- **Capture group support**: The replacement string supports numeric indices (`$1`, `$0`), named groups (`$name`, `${name}`), and escaped dollars (`$$`).
- **Context awareness**: Replacements apply to both matching lines and context lines when using `-A`, `-B`, or `-C` flags.
- **Performance optimized**: The implementation reuses allocations through `Space<M>` to avoid per-line memory overhead during large searches.

## Frequently Asked Questions

### Does ripgrep's --replace modify files in place?

No. The `--replace` flag is strictly a display feature that transforms how matches appear in the terminal output. It never writes changes back to the source files. If you need to modify files in place, you should pipe ripgrep's output to a tool like `sed` or use a dedicated file modification utility.

### How do I use named capture groups with ripgrep --replace?

You can reference named capture groups using the `$name` syntax or the `${name}` syntax for disambiguation. For example, if your pattern includes `(?P<year>\d{4})`, you can use `--replace '${year}'` to insert the captured value. The braces are required if the name is followed immediately by other characters that could be interpreted as part of the name.

### Why does my shell break the $1 syntax in ripgrep replacements?

Shells like Bash and Zsh interpret `$1` as a shell variable before ripgrep ever sees it. Since these variables are typically unset, they expand to empty strings, breaking your replacement pattern. Always wrap your replacement argument in single quotes, such as `--replace '$1 $2'`, to prevent the shell from expanding the dollar signs.

### Can I use ripgrep replacement with multiline patterns?

Yes. The replacement engine in [`crates/printer/src/util.rs`](https://github.com/BurntSushi/ripgrep/blob/main/crates/printer/src/util.rs) detects multiline searches and handles them appropriately. For single-line searches, it strips line terminators to ensure look-around assertions work correctly, then performs the replacement. For multiline patterns, the replacement applies across the full matched text, preserving the ability to use capture groups that span multiple lines.