Jon Eskin's Blog

CLI Workflow with Grep, Fzf, and Clp

Introduction

grep, ag, and ripgrep search file contents for text that matches regular expressions. The previous links show some of their more advanced options.

fzf is an interactive Unix filter. You can feed it anything from stdin (files, previously entered commands, etc) and it displays the results on the screen along with a prompt. When you type into the prompt, fzf filters the list.

When combined, they can substantially speed up a CLI workflow.

fzf overview

The command “fzf” on its own populates the list with all files in the current directory and its subfolders.

If you’ve installed fzf from a distribution package manager, pressing Ctrl+r at a shell prompt populates the list with show your command history.

As you type into the prompt, fzf narrows the results by fuzzy matching (e.g. typing “vcaa” matches vim ~/.config/aerc/aerc.conf). I find this very useful for digging up infrequently used commands involving git, ssh, or the system clipboard.

The --preview option will kick off a program in a subprocess and display its output in a tiled window.

The following command uses clp to fill the preview window. clp takes a file and writes it to stdout with syntax highlighting. fzf will provide the filename for each result, feed it to clp, and display the output in the righthand pane.

fzf --preview 'clp {}'

fzf_clp

You can scroll the preview window by holding shift pressing up/down, or by using your mouse wheel.

Combination

You can combine fzf and grep tool such as grep with fzf with a Unix pipe. Let’s see how that works.

We’ll start by grepping for the the word “time”, chosen for just for demonstrative purposes:

grep --recursive --line-number --binary-files=without-match "time" .

Here’s grep’s output, run in the Hare programming language’s source directory:

grep

We can tell fzf to pick apart the output; we state that it’s colon delimited and that we want to pass the first column (the filename) to clp:

grep --recursive --line-number --binary-files=without-match "time" . | fzf --delimiter=':' --preview 'clp {1}'

fzf_grep

Observe that the second column of grep’s output contains the line number of the match.

We can tell fzf to center the output of the preview window on the matched line and tell clp to highlight the line with the -h flag.

grep --recursive --line-number --binary-files=without-match "time" . | fzf --delimiter=':' -n 2.. --preview-window '+{2}-/2' --preview 'clp -h {2} {1}'

fzf_clp_line

Finally, we can bind a key to open the file in a text editor and jump to the location of the matched line. When we quit the editor, we’ll be right back where we were in fzf.

grep --recursive --line-number --binary-files=without-match "time" . | fzf --delimiter=':' -n 2.. --preview-window '+{2}-/2' --preview 'clp -h {2} {1}' --bind 'ctrl-o:execute(vim +{2} {1} < /dev/tty)'

Note that you can trivially swap out grep for ag or ripgrep and it’ll work just fine.

ag --color --line-number "time" | fzf --delimiter=':' -n 2.. --preview-window '+{2}-/2' --preview 'clp -h {2} {1}' --bind 'ctrl-o:execute(vim +{2} {1} < /dev/tty)'

The command has grown pretty long. I tend to throw everything after the first pipe in a script on my path and pipe grep commands to that instead.

I use this script often; it’s handy for quickly jumping back and forth between fzf and your editor to make a series of quick edits.

asciicast

In conclusion, we’ve seen that this approach provides editor agnostic searching and file previewing. Having grep at your fingertips allows you to effortlessly make elaborate searches without the constraints of editor plugins.