Skip to content

Instantly share code, notes, and snippets.

@reegnz
Last active April 23, 2024 18:36
Show Gist options
  • Star 44 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save reegnz/b9e40993d410b75c2d866441add2cb55 to your computer and use it in GitHub Desktop.
Save reegnz/b9e40993d410b75c2d866441add2cb55 to your computer and use it in GitHub Desktop.
Implementing a jq REPL with fzf

Implementing a jq REPL with fzf

Update: I created jq-zsh-plugin that does this.

One of my favourite tools of my trade is jq. It essentially enables you to process json streams with the same power that sed, awk and grep provide you with for editing line-based formats (csv, tsv, etc.).

Another one of my favourite tools is fzf. Essentially it is just a command line fuzzy finder, that has some awesome interactive uses.

Now in this REPL case it's fuzzyness isn't particularly necessary, but lucky for me it also has an awesome preview functionality as well.

I took this idea from another blog post about turning simple commands into a live REPL

The full code is the following:

#!/usr/bin/env bash
if [[ -z $1 ]] || [[ $1 == "-" ]]; then
    input=$(mktemp)
    trap "rm -f $input" EXIT
    cat /dev/stdin > $input
else
    input=$1
fi

echo '' \
    | fzf --phony \
          --preview-window='up:90%' \
          --print-query \
          --preview "jq --color-output -r {q} $input"

Let's walk through what the script does.

Input handling

if [[ -z $1 ]] || [[ $1 == "-" ]]; then
    input=$(mktemp)
    trap "rm -f $input" EXIT
    cat /dev/stdin > $input
else
    input=$1
fi

The first part is just input handling. If there is an argument, it assumes it's a json file to be fed to fzf.

If there is no argument, or the first argument is -, then it assumes it needs to cache the input coming from stdout. It caches the input into a temporary file which it will delete on exit with a trap statement.

FZF preview

fzf has a --preview mode that runs a pre-defined command when you change your query input. The --print-query option will print out the query you wrote as the first line once fzf quits.

echo '' | fzf --print-query --preview 'echo {q}'

Feeding fzf with an empty input will mean no fuzzy search can be done, but whatever input you feed it can be used in the preview window and it updates the preview whenever the query changes. This is where we will hoist our REPL onto.

Core REPL code

echo '' | fzf --print-query --preview 'jq {q} input.json'

That's it, our REPL is done! I find this small form fascinating. Now what is still missing, that landed in the final script:

  1. parameterize the jq input with any file
  2. capture standard input, buffer it and use that as the jq input
  3. formating the preview window

All of the above is implemented in the first example code.

After I've writen this post I've also found this nice asciicast that shows a generalized fzf REPL idea in action.

#!/usr/bin/env bash
if [[ -z $1 ]] || [[ $1 == "-" ]]; then
input=$(mktemp)
trap "rm -f $input" EXIT
cat /dev/stdin > $input
else
input=$1
fi
echo '' \
| fzf --phony \
--preview-window='up:90%' \
--print-query \
--preview "jq --color-output -r {q} $input"
@reegnz
Copy link
Author

reegnz commented Jan 23, 2020

@pvonmoradi
Copy link

Interesting script! Here are some suggestions:

  • Double-quote the variable in lines 4 and 5 (shellcheck)
  • As of fzf version 0.25.0, --phony option has been renamed to --disabled

Here is the patched script:

#!/usr/bin/env bash
if [[ -z $1 ]] || [[ $1 == "-" ]]; then
    input=$(mktemp)
    trap 'rm -f "$input"' EXIT
    cat /dev/stdin > "$input"
else
    input=$1
fi

echo '' \
    | fzf --disabled \
        --preview-window='up:90%' \
        --print-query \
        --preview "jq --color-output -r {q} $input"

@reegnz
Copy link
Author

reegnz commented Jun 29, 2021

Thanks for that feedback!
Continued improvements to this script live here: https://github.com/reegnz/jq-zsh-plugin
I will try to get the improvements in there, but PR-s are also welcome!

@davidkuda
Copy link

This is ingenious! Thanks so much for sharing it, a lot of fun to use. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment