December 18, 2017

Day 18 - Awesome command-line fuzzy finding with fzf

By: Nick Stielau (@nstielau)

Edited By: Sascha Bates (@sascha_d)

It’s all about the CLIUX

Ok, CLIUX is not a word, but command-line interface user experience is definitely a worthy consideration, especially if you use the command line on a daily basis (or, hourly). Web apps and graphical user interfaces usually get the design love, but this holiday season, treat yourself to a great command line user experience.

Now that we’re thinking about CLIUX, we can roll up our sleeves and see how we can improve some of our everyday command line operations. This can be as simple as adding in some colors to our shell prompts or big as changing over to a new shell.

We’re going to focus on a few ways to make finding things easier.

Enter FZF

A lot of CLI is about finding stuff. Sometimes that is finding the right files to edit, or searching through git logs to review, or finding a a process Id or user Id to use as a parameters for other commands (kill, chgrp, etc).

Fzf is a fuzzy finder, a CLI tool that is explicitly meant to bridge the technical need to find things with the user need to find them easily. It has been around for a few years, but this is the year you use it to make your CLIUX awesome.

FZF Logo

FZF adheres to the Unix Philosophy of doing one thing, and doing it well and valuing composability. These two characteristics make it likely we can find lots of ways to use fuzzy finding, and to integrate it into our daily operations.

Quick shoutout to junegunn for authoring fzf and Tschuy for introducing fzf to me.

Using FZF

Getting started

You can install FZF with package managers, brew install fzf for OSX and dnf install fzf for Fedora, or check out the full installation instructions for other options. Once you’ve got it installed, we’ll start fuzzy finding!

Step 1) Piping data into FZF

We’ll start off looking at how we can pipe an input set to fzf and doing some fuzzy finding. Fzf can fuzzy search any input from stdin. For starters, let’s pipe our dictionary through fzf,

cat /usr/share/dict/words | fzf

and start typing to narrow down the list to matching words: c l n j f i. Ok, so there is one word out of the 235,886 in the dictionary that contains those letters in that order. Clanjamfrie.

Searching for clanjamfrie

It’s a Scottish word that means spoken nonsense, as in"Anyone who doesn’t like fuzzy finding is just spouting clanfamfrie.“ Who knew?

Step 2) Piping data out of FZF

Now we are getting somewhere. Selecting a dictionary word is pretty useful, but how can we make this really great? That’s right, piping our selected word to cowsay.

cat /usr/share/dict/words | fzf | cowsay
fzf and cowsay FTW

Step 3) Live preview

Sysadmins everywhere are on the edge of their seats. Cowsay, dictionaries, fuzzy finding. It’s almost too much. How can we take this to the next level?

Using the --preview flag, we can specify a program for a live preview of our fzf selection. In this case, we can get a preview of exactly how the cow will say it:

cat /usr/share/dict/words | fzf --preview "cowsay {}" | cowsay
Preview that moo

Step 4) Gitting More Pragmatic

Not that cowsay isn’t a real world use-case, but let’s get into something more pragmatic, like… git! This gem of an example is from the ample examples on the fzf wiki (although paired down a bit for consumability here). It shows how to search though git logs and examine a diff. ”`

fshow - git commit browser

fshow() { git log –graph –color=always \ –format=“%C(auto)%h%d %s %C(black)%C(bold)%cr” “$@” | fzf –ansi –bind “enter:execute: (grep -o ‘[a-f0-9]{7}’ | head -1 | xargs -I % sh -c ‘git show –color=always % | less -R’) << ‘FZF-EOF’ {} FZF-EOF” } “`

The big new addition here is the --bind option, which can specify a program to execute upon key press or selection. The fshow function uses this functionality to view the git diff with less when enter is pressed.

Here’s the fshow function running against the fzf codebase:

Finding a git commit

Step 5) Make viewing diffs easy

This is pretty cool, but we already know how to make it cooler. With --preview!

fshow() {
  git log --graph --color=always \
      --format="%C(auto)%h%d %s %C(black)%C(bold)%cr" "$@" |
  fzf --ansi --preview "echo {} | grep -o '[a-f0-9]\{7\}' | head -1 | xargs -I % sh -c 'git show --color=always %'" \
             --bind "enter:execute:
                (grep -o '[a-f0-9]\{7\}' | head -1 |
                xargs -I % sh -c 'git show --color=always % | less -R') << 'FZF-EOF'
                {}
FZF-EOF"
}
Previewing diffs

Wow! Does anyone else feel like we just implemented Github in like 6 lines?!?!

Step 6) Man Explorer

Or, whip up a man page explorer with search and live-preview. Once you have the format down, you can imagine more use-cases that boil down to fuzzy finding with a detailed preview that you can select to execute additional commands, optionally falling back to the beginning.

# This is ugly.  Refactoring left as exercise to reader...
function  mans(){
man -k . | fzf -n1,2 --preview "echo {} | cut -d' ' -f1 | sed 's# (#.#' | sed 's#)##' | xargs -I% man %" --bind "enter:execute: (echo {} | cut -d' ' -f1 | sed 's# (#.#' | sed 's#)##' | xargs -I% man % | less -R)"
}
Man Page Explorer

Why I can’t live without FZF

Ok, I guess I could live. But my command line useage would be a little sadder. For example, I use these kubernetes config helpers every day. In addition to saving some time, I get a little bit more joy out of the fzf implementations. I love the command line, but that doesn’t mean I don’t want a great user experience. Heck, I deserve one.

# short alias for picking a Kube config
# Find cluster definitions in ~/.kube and save one as variable.
c () {
  export KUBECONFIG=$(find ~/.kube -type f -name '*'"$1"'*' -exec grep -q "clusters:" {} \; -print | fzf --select-1)
}

# helper for setting a namespace
# List namespaces, preview the pods within, and save as variable
ns () {
    namespaces=$(kubectl get ns -o=custom-columns=:.metadata.name)
    export NS=`echo $namespaces | fzf --select-1 --preview "kubectl --namespace {} get pods"`
    echo "Set namespace to $NS"
}

# short alias that uses chosen namespace
k () {
    kubectl --namespace=${NS:-default} $@
}

Choosing a namespace:

Kubernetes Namespace Chooser

Conclusion

If you want to spruce up your command line user experience, dig in with fzf and put the power of fuzzy finding to use. Junegunn’s fzf is a simple, composable tool that can make your command line more efficient and usable for 2018.

References, Details and Inspiration

No comments :