A typo-tolerant which. You meant grep, you typed gerp — witch
finds it anyway and prints the same absolute paths which would.
$ witch gerp
/usr/bin/grep
$ which gerp
gerp not found
Exact names behave exactly like which; misspelled names are fuzzy-matched
against every executable on your PATH and ranked by similarity. When the
output is captured — command substitution or a pipe — witch prints only the
best match, so $(witch gerp) always expands to exactly one path.
- Typo-tolerant lookup — Jaro-Winkler scoring handles transpositions
(
grpe→grep), wrong letters (pyhton3→python3), and case slips - Exact-match short-circuit — a correctly spelled command is resolved
byte-for-byte like
which, fuzzy matching never engages - Command-substitution safe — non-TTY stdout automatically collapses to
the single best match;
$(witch gerp)just works - Quality-cliff ranking — shows the few plausible candidates, not a wall of noise: results stay close to the top score and the list ends at the first sharp drop (hard cap 10)
- Interactive picker —
witch -i graprenders a menu on stderr and prints the chosen path to stdout, so it even works inside$( ) which-compatible behavior — first-in-PATHshadowing, one path per line, exit codes0/1, multiple command arguments resolved in order
Latest release: v0.2.0 —
drop-in which compatibility (BSD + GNU flag superset and --strict mode). All
methods below install it.
brew install codedeviate/cli/witchcargo install witch-cli(The crate is witch-cli; the installed binary is witch.)
git clone https://github.com/codedeviate/witch.git
cd witch
cargo install --path .Requires Rust 1.85+. Unix-only (macOS, Linux) — executable detection uses Unix permission bits.
witch grep # exact lookup, same as which
witch gerp # typo-tolerant lookup, best matches first
witch -a pyhton # all candidates, even when piped
witch -1 grap # only the best match
$(witch gerp) -V # command substitution: exactly one path
witch -i grap # interactive picker (menu on stderr)
witch -q gerp # no output, exit code onlywitch [OPTIONS] <COMMANDS>...
Arguments:
<COMMANDS>... Command name(s) to look up
Options:
-1, --first Print only the best match
-a, --all List all matches, including PATH duplicates, even when not a TTY
-q, --quiet No output, exit code only
-s Silent; alias of --quiet (BSD which -s)
-i, --pick Interactively pick from the candidates
--strict Disable fuzzy matching; behave byte-for-byte like which
--skip-dot Skip PATH entries that start with `.`
--skip-tilde Skip PATH entries that start with `~` and entries under $HOME
--show-dot Print ./prog for dot-relative PATH entries
--show-tilde Print ~/... for matches under $HOME (ignored as root)
--tty-only Honor --show-dot/--show-tilde only when stdout is a TTY
--examples Print usage examples and exit
-h, --help Print help
-V, --version Print version
GNU which's --read-alias/--skip-alias/--read-functions/--skip-functions are
accepted for wrapper compatibility but are no-ops (witch does not parse shell
aliases or functions).
-1/-a/-i/-q with contradictory intents conflict (exit 2) instead of
silently overriding each other: -1 vs -a, and -i vs any of -1/-a/-q.
| Situation | Output |
|---|---|
| Exact name match | that single path, always |
| stdout is a TTY | ranked candidates, one path per line, best first |
stdout is not a TTY (pipe, $( )) |
best match only |
-1 |
best match only, regardless of TTY |
-a |
full ranked list, regardless of TTY |
-i with 2+ candidates |
menu on stderr, chosen path on stdout |
witch accepts the union of BSD and GNU which flags. To use it as a literal which replacement, symlink it under the name which:
ln -s "$(command -v witch)" ~/.local/bin/whichWhen invoked as which, witch auto-enables strict mode: fuzzy matching is disabled and behavior is byte-for-byte which — exact lookups only, silent on not-found, exit 0/1. Use --strict to get the same behavior under the witch name. In strict mode, -a lists every instance on PATH (including duplicates from repeated PATH entries), exactly like BSD which -a.
--show-dotand--show-tildeapply to exact (which-style) matches. Fuzzy-fallback results are always printed as plain absolute paths.--tty-onlygates the display flags and the interactive picker: when stdout is not a TTY,--show-dot/--show-tildeare dropped and-iwill not render a menu.- Under
--strict(or when invoked aswhich), fuzzy matching is off, so-i/--pickhas nothing to pick from and is effectively a no-op.
Candidates are every executable on PATH (deduped by name, first occurrence
wins — the same shadowing rule which uses). Each name is scored against
your query with case-insensitive Jaro-Winkler similarity:
- Scores below 0.8 are discarded (keeps junk from matching short names
like
xz) - Survivors are ranked best-first, ties broken alphabetically
- The list keeps only results within 0.10 of the top score, ends at the first drop greater than 0.05 between neighbors, and caps at 10
| Code | Meaning |
|---|---|
0 |
every query produced at least one match |
1 |
at least one query found nothing (or the picker was cancelled) |
2 |
usage error (unknown/conflicting flags, no arguments) |
A query with no match prints witch: no match for 'xyz' to stderr.
MIT — see LICENSE.