A fast, interactive command-line fuzzy finder.
Static preview — run vhs docs/demo.tape to record an animated GIF (see docs/demo.tape).
sift takes any list of lines — files, command history, git branches, anything —
and lets you narrow it down to the one you want by typing just a few letters.
The characters you type only have to appear in order, not next to each other,
so rprt finds report_2024_final.txt. Matches are ranked so the most likely
result floats to the top.
Pipe a list in, type to filter, press Enter, and sift prints what
you picked.
- Fuzzy matching with smart ranking — word boundaries, camelCase humps, and consecutive runs are rewarded; long gaps are penalised.
- Extended search syntax — combine fuzzy,
'exact,^prefix,suffix$, and!inverseterms in one query. - Fast — matching is parallelised across every CPU core.
- Preview window — show file contents, a
git diff, anything, for the highlighted item. - Multi-select — mark several items with Tab.
- Shell key-bindings — Ctrl-T (files), Ctrl-R (history), Alt-C (cd) for bash, zsh, and fish.
- Single static binary — no runtime dependencies; trivial to distribute.
Requires Go 1.25 or newer (older toolchains are fetched
automatically by go build).
go install github.com/Anshika2203/sift@latest…or clone and build:
git clone https://github.com/Anshika2203/sift.git
cd sift
make build # produces ./sift (sift.exe on Windows); or: go build -o sift .Homebrew (macOS/Linux):
brew install --cask Anshika2203/tap/sift
# or tap once, then use the short name:
# brew tap Anshika2203/tap
# brew install --cask siftScoop (Windows):
scoop bucket add Anshika2203 https://github.com/Anshika2203/scoop-bucket
scoop install siftDebian/Ubuntu, Fedora/RHEL, Alpine — grab the package from the latest release:
sudo dpkg -i sift_*_linux_amd64.deb # Debian/Ubuntu
sudo rpm -i sift_*_linux_amd64.rpm # Fedora/RHEL
sudo apk add --allow-untrusted sift_*_linux_amd64.apk # AlpineWhy not a bare
brew install sift? The short form (likebrew install fzf) only works for tools accepted into Homebrew's official homebrew-core catalog, which is curated by Homebrew's maintainers and requires a project to be notable and well-established — you can't self-publish there. A personal tap always uses theowner/tap/prefix (or a one-timebrew tap). Oncesiftgains traction it can be submitted to homebrew-core, after whichbrew install siftwould work for everyone. (Scoop, by contrast, lets you use the barescoop install siftas soon as the bucket is added.)
📖 See COOKBOOK.md for a full cross-platform cookbook — every feature with PowerShell (Windows), Bash/Zsh (macOS/Linux), and Fish examples.
# Pick a file
find . -type f | sift
# Pick a git branch and check it out
git branch | sed 's/^[* ] //' | sift | xargs git checkout
# Preview file contents while you browse
find . -type f | sift --preview 'cat {}'
# Preview on the bottom, 40% tall
find . -type f | sift --preview 'cat {}' --preview-window 'down,40%'
# Multi-select with Tab
ls | sift --multiWith nothing piped in, sift lists files in the current directory:
siftBy default a query is a fuzzy match. Separate the query with spaces to add more terms — every term must match (logical AND). Markers change how a term matches:
| Token | Match type | Example | Matches |
|---|---|---|---|
foo |
fuzzy | fbb |
FooBarBaz |
'foo |
exact substring | 'bar |
foo**bar**baz |
'foo' |
exact at word boundary | 'wild' |
a **wild** thing (not wildcard) |
^foo |
prefix | ^main |
main.go |
foo$ |
suffix | .go$ |
main.go |
^foo$ |
exact equality | ^README.md$ |
README.md |
!foo |
inverse (must not match) | !test |
excludes main_test.go |
a | b |
OR | go$ | rb$ |
main.go or app.rb |
The inverse marker combines with the others: !^foo, !foo$, !'foo.
# files containing "main", but not "test"
find . | sift --query 'main !test'
# Go or Ruby files
find . | sift --query 'go$ | rb$'Matching is case-insensitive unless a term contains an uppercase letter
(smart case). Override with -i/+i, or make terms exact by default with
-e/--exact.
Restrict the search (or the display) to specific columns. Fields are split on
whitespace by default, or on --delimiter. Indexes are 1-based; negatives count
from the end; 2.., ..3, and 2..4 are ranges.
# search only the 2nd column, but keep the whole line
ps aux | sift --nth 2
# colon-separated, search the first field
sift -d ':' --nth 1 < /etc/passwd
# show only the last field while searching it
sift --with-nth -1--preview CMD runs a command for the highlighted item and shows its output in
a side pane. These placeholders are expanded (and shell-quoted) in CMD:
| Placeholder | Expands to |
|---|---|
{} |
the current item |
{q} |
the current query |
{n} |
the current item's index |
{+} |
all selected items (or the current one) |
{1}, {-1}, {2..3} |
field(s) of the current item |
# git branch picker with a diff preview
git branch | sed 's/^[* ] //' | sift --preview 'git log --oneline {1}'Position and size it with --preview-window (e.g. up,40%, left,60%,
hidden). In the finder, Ctrl-O toggles the preview and
Shift/Alt+↑/↓ scroll it.
| Flag | Description |
|---|---|
-q, --query STR |
start with an initial query |
-p, --prompt STR |
set the prompt (default > ) |
-m, --multi |
enable multi-select |
--preview CMD |
run CMD for the highlighted item (see placeholders below) |
--preview-window S |
[up|down|left|right][,SIZE[%]][,hidden] (default right,50%) |
--ansi |
parse ANSI color codes in the input |
--layout L |
reverse (top-down, default) or default (bottom-up) |
--reverse |
shorthand for --layout reverse |
--cycle |
wrap-around cursor movement |
--no-mouse |
disable mouse (wheel + click are on by default) |
--color SPEC |
theme, e.g. prompt:cyan,hl:green,pointer:red |
--history FILE |
load/save query history (Ctrl-P/Ctrl-N) |
--border[=STYLE] |
draw a border (rounded, sharp, …) |
--margin TRBL / --padding TRBL |
space outside / inside the border |
--style PRESET |
default, minimal, or full |
--header STR |
show fixed header line(s) above the list |
--footer STR |
sticky footer line(s) at the very bottom |
--bind K:ACT[,..] |
bind keys/events to actions (repeatable) — see below |
--jump-labels STR |
label characters for the jump action |
--listen [A:]PORT |
HTTP control server (local only) |
--header-lines N |
treat the first N input lines as a sticky header |
-e, --exact |
exact-match by default (' flips a term to fuzzy) |
-i / +i |
force case-insensitive / case-sensitive matching |
--algo v1|v2 |
fuzzy algorithm: optimal v2 (default) or greedy v1 |
--tiebreak C[,..] |
tie-break order: length, begin, end, index |
-n, --nth N[,..] |
limit the search to certain fields |
--with-nth N[,..] |
display only certain fields |
-d, --delimiter STR |
field delimiter (default: whitespace) |
+s, --no-sort |
keep input order instead of ranking by score |
--tac |
reverse the input order |
-f, --filter STR |
non-interactive: print matches and exit |
-1, --select-1 |
if exactly one item matches, pick it without the UI |
-0, --exit-0 |
if nothing matches, exit immediately |
--print-query |
print the final query as the first output line |
--expect KEYS |
extra accept keys; prints which key was pressed first |
--read0 / --print0 |
NUL-separated input / output |
--bash / --zsh / --fish |
print the shell key-binding script |
-V, --version |
print version |
-h, --help |
show help |
| Variable | Effect |
|---|---|
SIFT_DEFAULT_COMMAND |
command run to produce input when stdin is a terminal |
SIFT_DEFAULT_OPTS |
default options prepended to every invocation |
SIFT_CTRL_T_COMMAND |
command used by the CTRL-T key-binding |
| Key | Action |
|---|---|
| ↑ / Ctrl-P, ↓ / Ctrl-N | move cursor |
| PgUp / PgDn | page up / down |
| Enter | accept selection |
| Esc / Ctrl-C | cancel |
| Tab | mark item (with --multi) |
| Ctrl-U | clear query |
| Ctrl-W | delete word |
| Backspace | delete character |
| Ctrl-O | toggle the preview window |
| Shift/Alt + ↑/↓ | scroll the preview |
Add the key-bindings to your shell to get Ctrl-T, Ctrl-R, and Alt-C:
# bash — in ~/.bashrc
eval "$(sift --bash)"
# zsh — in ~/.zshrc
eval "$(sift --zsh)"
# fish — in ~/.config/fish/config.fish
sift --fish | sourceThe bash and zsh scripts also enable fuzzy completion: type the trigger
** and press Tab to complete paths, e.g. vim **<Tab> or
cd **<Tab>. Customise with SIFT_COMPLETION_TRIGGER and
SIFT_COMPLETION_COMMAND.
Map keys (or events) to actions: --bind 'KEY:ACTION[+ACTION...]', repeatable.
Actions: up down page-up page-down first last · accept
abort toggle toggle-all select-all deselect-all · clear-query
backward-delete-char change-query(..) put(..) change-prompt(..) ·
toggle-preview preview-up preview-down · jump jump-accept ·
backward · reload(..) execute(..) execute-silent(..) become(..)
Events: start, change, focus
Placeholders in command actions: {} {q} {n} {+} {1}/{-1}/{2..3}.
# open the highlighted file in your editor, replacing sift
find . -type f | sift --bind 'enter:become(${EDITOR:-vim} {})'
# live grep: re-run ripgrep on every keystroke
sift --bind 'change:reload(rg --line-number {q} || true)'
# jump mode: press ctrl-j, then a label key to leap to a row
sift --bind 'ctrl-j:jump'
# folder navigation: → descends, ← goes back up (preview intact)
find . | sift --preview '[ -d {} ] && ls {} || cat {}' \
--bind 'right:reload(find {} -maxdepth 1)' --bind 'left:backward'backward undoes the last reload, which is what makes ← "go up a level".
--listen [addr:]port starts a local HTTP server so another program can POST an
action string (e.g. reload(ls)) to a running sift.
input ──▶ reader ──▶ matcher ──▶ ui
(stdin or (lines) (ranked (interactive
file walk) matches) list + preview)
internal/algo— the match + scoring engine. Fuzzy matching uses a greedy two-pass aligner (forward scan to locate the match, backward scan to tighten it) followed by a linear scoring pass that applies the boundary / camelCase / consecutive bonuses and gap penalties. The same scoring pass backs the exact / prefix / suffix / equal modes.internal/pattern— parses the extended search syntax into terms and evaluates them against an item.internal/matcher— runs the pattern across every item in parallel and sorts the results (score, then length, then input order).internal/reader— reads lines from stdin, or walks the filesystem when stdin is a terminal.internal/ui— the full-screen interactive interface (built on tcell), including the async preview pane.
make build # compile ./sift
make test # run the test suite
make cross # build release binaries for all platforms into dist/Releases are produced with GoReleaser (config in
.goreleaser.yml), which builds the binaries and the Homebrew / Scoop / deb /
rpm / apk artifacts:
git tag v0.1.0
git push --tags
GITHUB_TOKEN=... goreleaser release --cleanThe
brews:andscoops:steps publish toAnshika2203/homebrew-tapandAnshika2203/scoop-bucket; create those repos first, or comment the sections out to skip them.