Home | Posts by Category

Use an External Fuzzy-finder for CLI Completion


CLI fuzzy-finders are really great. You feed them options via stdin, fuzzy-find the match you want, then get the result via stdout (ready to be processed by additional shell tools). Simple, fast, immediately useful, and if you can already write a shell script there’s nothing new to learn.

printf '%s\n' foo bar baz quux | fzy | xargs echo 'The result is'

There’s a bunch. fzf is the most popular and featureful, but it’s huuuuge at 10k LOC. fzy is an excellent and fast alternative with great matching at just 1200 lines of C. One of the great things about the simple stdin/stdout contract is you can easily swap out one with another as you’re testing behahvior, UI, matching, etc.

Shell Completion

Shell completion usually isn’t as convient or fast as a fuzzy-finder. Even super fancy, menu-centric completion like in Zsh isn’t as nice. Not even close. Plus shell completion is difficult and time-consuming to write. It’s very shell-specific code and often archaic. A very stark contrast to the stdin/stdout contract.

It would be nice if we could invoke a fuzzy-finder instead of the built-in shell completion.

Turns out that’s pretty simple with Zsh:

fzy-completion() {
    setopt localoptions localtraps noshwordsplit noksh_arrays noposixbuiltins

    # Get what the user has typed in so far.
    local tokens=(${(z)LBUFFER})

    # Pluck out the first argument which is probably the command.
    local cmd=${tokens[1]}

    # Invoke a function or script and grab the result.
    local result=$($run_something "${tokens[@]}")

    # Put the result back on the CLI where the user left off.
    if [ -n "$result" ]; then

    zle reset-prompt

# Invoke completion when the user presses ctrl-f.
zle -N fzy-completion
bindkey '^F' fzy-completion

You can see a full, working implementation here:


It works by searching for a shell function or a shell script on $PATH that matches the pattern _fzy_<cmd> where cmd is the command the user typed. This pattern makes it dead-simple to write arbitrary completion functions in just a few lines of whatever script language you care to write.


You can see several full examples in this directory:


Example Demos

cd to a child directory:

Complete an arbitrary file path:

Complete Git branches or tags:

Complete from your shell history (if you haven’t yet typed a command):

Complete process IDs for kill:

Search and complete from all available manpages:

Complete known ssh hosts:

Complete npm run scripts from a package.json file. (And it won’t try to complete other npm sub-commands.)