Claude
Skills
Sign in
Back

fuzzy-filter

Included with Lifetime
$97 forever

The non-interactive `rg`/`fd`/`rga` → `fzf --filter` pipeline — scan with a regex tool, then fuzzy-rank the lines without opening a TUI. Use when you want fuzzy (not regex) matching of file paths, code lines, or document/PDF text from a script or one-shot command, when reproducing the fuzzy-search MCP's behavior on the bare command line, or when piping fuzzy-ranked results into xargs/an editor. This is the CLI technique; for the INTERACTIVE picker use the fzf skill, and for tool-call ergonomics use the fuzzy-search MCP server.

AI Agents

What this skill does


# fuzzy-filter — Non-Interactive Fuzzy Search Pipelines

## Overview

`fzf` is famous as an **interactive** picker, but it also has a batch mode:
`fzf --filter QUERY` reads lines on STDIN, fuzzy-ranks them against `QUERY`,
prints the matches to STDOUT, and exits — **no terminal UI, no keyboard, fully
scriptable**. Pair it with a fast scanner and you get a two-stage pipeline:

```
                regex / literal scan            fuzzy rank
  files/dirs ──────────────────────▶ lines ──────────────────▶ ranked matches
                rg · fd · rga                  fzf --filter QUERY
```

1. **Stage 1 — scan (regex):** `rg`, `fd`, or `rga` walks the tree and emits
   candidate lines (file paths, `file:line:content` rows, or document text).
   This stage is gitignore-aware and uses **regular expressions**.
2. **Stage 2 — rank (fuzzy):** `fzf --filter` keeps only the lines that fuzzy-match
   `QUERY` and orders them best-first. This stage uses **fzf's fuzzy syntax — NOT
   regex** (see [Fuzzy filter syntax](#fuzzy-filter-syntax)).

The two query languages are different on purpose. Use a broad/empty Stage-1
pattern (`.` matches every line) to hand fzf the full candidate set, then let the
fuzzy query do the selective work — or pre-narrow with a real regex in Stage 1
when you know it, and fuzzy-rank the remainder.

> This is exactly how the **fuzzy-search MCP server** works internally; this
> skill documents the underlying commands so you can run them directly. See
> [references/pipelines.md](references/pipelines.md) for the exact
> MCP-equivalent command behind each tool.

## When to Use This Skill (and When Not To)

Use this **non-interactive** technique when you want fuzzy results from a script,
a single command, or to pipe ranked output onward — no TUI involved.

| You want to… | Use | Why |
|---|---|---|
| Fuzzy-rank lines in a script / one-shot, pipe to xargs or an editor | **this skill** (`… \| fzf --filter`) | Batch mode, deterministic STDOUT, no TTY needed |
| Interactively pick from a live, keystroke-updated list | **fzf skill** | `fzf` (no `--filter`), previews, `--bind`, live-grep |
| Get fuzzy results as a structured tool call (paths/line numbers/JSON) | **fuzzy-search MCP** (`mcp__plugin_fuzzy-search_fuzzy-search__*`) | Wraps these same pipelines with typed args + parsed output |
| Plain **regex** content search (no fuzzy ranking) | **ripgrep skill** | `rg` alone already does this; don't add fzf |
| Find files by **name/glob/metadata** (no fuzzy ranking) | **fd skill** | `fd` alone already does this |

Key distinctions:
- **vs. the fzf skill:** that skill covers *interactive* selection (previews,
  keybindings, `CTRL-T`/`CTRL-R`, live-grep frontends). Here `fzf` never draws a
  UI — `--filter` makes it a Unix filter.
- **vs. the fuzzy-search MCP:** the MCP exposes these pipelines as model tools
  with typed arguments and parsed results. Reach for it when you want tool-call
  ergonomics; reach for this skill when you're already in a shell.
- **vs. rg/fd alone:** if a **regex** answers the question, you don't need fzf.
  Add `fzf --filter` only when you specifically want **fuzzy** (typo-tolerant,
  subsequence) ranking.

## Prerequisites

```bash
rg --version      # ripgrep — Stage-1 scanner for files & content
fzf --version     # fzf     — Stage-2 fuzzy ranker (needs batch --filter)
```

Required for everything: **`rg`** and **`fzf`**. Optional, per use case:

- **`fd`** — ergonomic file-list source for fuzzy *file* search (alternative to `rg --files`).
- **`rga`** ([ripgrep-all](https://github.com/phiresky/ripgrep-all)) — for **document/PDF** content (PDFs, Office docs, ebooks, archives, sqlite). `(rga 0.10+)`
- **`pdftotext`** (poppler) or **PyMuPDF** — to extract/inspect PDF pages once you've located a hit. See [references/pdf.md](references/pdf.md).

If a required binary is missing, **stop and tell the user** — do not auto-install.
See the **ripgrep**, **fd**, and **fzf** skills for installation instructions.

## Fuzzy Filter Syntax

Stage 2 uses fzf's **extended-search syntax**, the same language as the
interactive prompt. A bare term is a **fuzzy** (subsequence) match; prefix/anchor
sigils make terms exact. Space-separated terms are **AND**ed.

| Token | Match | Example |
|---|---|---|
| `sbtrkt` | Fuzzy (subsequence) | matches `SubstringTracking` |
| `'wild` | Exact substring | contains `wild` literally |
| `^core` | Prefix | starts with `core` |
| `.py$` | Suffix | ends with `.py` |
| `!fire` | Inverse | does **not** contain `fire` |
| `a$ \| b$ \| c$` | OR | ends with `a`, `b`, or `c` |

So `def test_ seer credit` means: fuzzy-`def test_` **AND** fuzzy-`seer` **AND**
fuzzy-`credit`, in any order. This is **not** a regex — `.`, `*`, `(`, `[` are
literal characters here, not metacharacters. For the full table, see the **fzf
skill's "Search Syntax"** section.

> **Smart-case applies to the fuzzy query too:** an all-lowercase query is
> case-insensitive; any uppercase letter makes it case-sensitive — the same rule
> rg/fd use.

## Fuzzy FILE Search

Feed fzf a list of paths and fuzzy-rank them by path.

```bash
# Source the file list with ripgrep (gitignore-aware), fuzzy-rank by path
rg --files | fzf --filter 'src model user'

# fd as the source instead (also gitignore-aware; matches names by default)
fd --type f | fzf --filter 'test util'

# Include hidden files in the candidate set
rg --files --hidden | fzf --filter 'config yaml'
```

**Bias ranking toward path structure** with fzf's path scheme — it weights
matches near path separators, which is what you usually want for filenames:

```bash
rg --files | fzf --scheme=path --filter 'cmd main'   # (fzf 0.36+)
```

### NUL-safety (paths with spaces/newlines)

A newline-delimited pipe breaks on filenames containing newlines. Make the whole
pipe NUL-delimited end-to-end:

```bash
rg --files --null | fzf --read0 --print0 --filter 'src model' \
  | xargs -0 -n1 echo            # hand off safely to the next tool

fd --print0 --type f | fzf --read0 --print0 --filter 'big report' \
  | xargs -0 "$EDITOR"
```

`--read0` makes fzf read NUL-separated input; `--print0` makes it emit
NUL-separated output. Always pair with `xargs -0`.

## Fuzzy CONTENT Search

Scan every line, then fuzzy-rank the `file:line:content` rows. This is the
workhorse pipeline.

```bash
rg --line-number --no-heading --color=never . PATH \
  | fzf --filter 'QUERY' --delimiter : --nth=1,3..
```

Stage 1 anatomy:
- `.` — match **every** line (hand the full set to fzf). Swap in a real regex to pre-narrow.
- `--line-number` — prefix each row with its line number (field 2).
- `--no-heading` — one self-contained `file:line:content` row per match (no grouped headers).
- `--color=never` — **mandatory**: ANSI color codes would corrupt fzf's matching and field splitting.

Stage 2 anatomy — the row is `FILE : LINE# : CONTENT`, so with `--delimiter :`:
- field **1** = file path, field **2** = line number, field **3..** = the line content (which may itself contain colons — `3..` captures all of it).
- `--nth=1,3..` — fuzzy-match against **path + content**, deliberately **skipping field 2 (the line number)** so digits in your query don't spuriously match line numbers.

### Content-only (ignore the path)

To match **only** the code/text and never the file path:

```bash
rg --line-number --no-heading --color=never . PATH \
  | fzf --filter 'QUERY' --delimiter : --nth=3..
```

`--nth=3..` restricts fuzzy matching to field 3 onward (content), ignoring both
the path (field 1) and the line number (field 2). Use this when a path component
keeps polluting your results.

> The full output row (including `file:line:`) is still **printed** — `--nth`
> only controls what fzf *matches on*, not what it emits. That's what lets you
> pipe the result into an editor at the right line.

## Field Scoping Deep-Dive

`--nth` is the lever that makes content search precise. It selects which
delimiter-separated fields fzf *searches*; `--with-nth` selects which fields

Related in AI Agents