Skip to content
Commitpedia
Log in Join & edit

jq cheat script: pretty-print, filter and flatten JSON from a file or stdin

A small read-only bash wrapper around jq with the recipes you keep looking up: pretty, compact, sorted keys, list paths, pick fields, filter array items, convert to CSV or TSV, and count. Works on a file or piped input.

Drafted with AI from official documentation. Scripts were run in a test environment and repository links verified. Awaiting a human check. Did it work for you? Last edited Outdated?

Edit history of jq cheat script: pretty-print, filter and flatten JSON from a file or stdin: 1 recorded edit
Edit history · 1 edit
Type
Script
Edits
1
Editors
1
First issued
15 Oct 2026
Last edit
15 Oct 2026
Language
bash
Licence
CC0-1.0

What this changes

Nothing on disk. It reads JSON from the file you name or from stdin, runs a jq filter and prints the result to stdout. It never writes to the input file.

How to undo it

Nothing to undo. To save a result, redirect it to a new file: jqc pretty data.json > data.pretty.json. Never redirect onto the input file itself; the shell would empty it before jq reads it.

jq is the standard tool for JSON on the command line, but its filter language is a second language to remember. This script wraps the dozen operations most people need behind plain subcommands, prints the jq filter it used so you learn it as you go, and reads either a file or stdin so it fits at the end of a curl or kubectl pipeline. It only reads.

The script

#!/usr/bin/env bash
set -euo pipefail

usage() {
  cat <<USAGE >&2
Usage: $(basename "$0") COMMAND [ARGS] [FILE]

Reads FILE, or stdin when FILE is missing or "-". Never modifies anything.

  pretty                 indent with 2 spaces
  compact                one line
  sorted                 pretty with keys sorted (good for diffs)
  keys                   top-level keys
  paths                  every path in the document, one per line
  type                   type of the top-level value
  count PATH             number of items in the array at PATH (e.g. .items)
  get PATH               value at PATH (e.g. .metadata.name), raw strings
  pick PATH K1,K2,...    for each item in array PATH keep only these keys
  where PATH KEY VALUE   items in array PATH whose KEY equals VALUE
  grep PATH KEY REGEX    items in array PATH whose KEY matches REGEX
  csv PATH K1,K2,...     array PATH to CSV with these columns (header first)
  tsv PATH K1,K2,...     same as csv but tab separated
  lines PATH             one compact JSON object per line (for xargs or while read)
  flatten                nested object to "a.b.c": value pairs
  raw FILTER             run any jq filter you type

Examples:
  curl -s https://api.github.com/repos/jqlang/jq | $(basename "$0") pick . name,stargazers_count
  kubectl get pods -o json | $(basename "$0") where .items .status.phase Running
USAGE
  exit 2
}

command -v jq >/dev/null 2>&1 || { echo "jq is not installed (apt install jq / brew install jq)" >&2; exit 1; }
[[ $# -ge 1 ]] || usage

cmd=$1; shift
args=()
file="-"

case "$cmd" in
  pretty|compact|sorted|keys|paths|type|flatten) [[ $# -le 1 ]] || usage ;;
  count|get|lines|raw) [[ $# -ge 1 && $# -le 2 ]] || usage ;;
  pick|csv|tsv) [[ $# -ge 2 && $# -le 3 ]] || usage ;;
  where|grep) [[ $# -ge 3 && $# -le 4 ]] || usage ;;
  -h|--help|help) usage ;;
  *) echo "unknown command: $cmd" >&2; usage ;;
esac

case "$cmd" in
  pretty|compact|sorted|keys|paths|type|flatten) [[ $# -eq 1 ]] && file=$1 ;;
  count|get|lines|raw) args=("$1"); [[ $# -eq 2 ]] && file=$2 ;;
  pick|csv|tsv) args=("$1" "$2"); [[ $# -eq 3 ]] && file=$3 ;;
  where|grep) args=("$1" "$2" "$3"); [[ $# -eq 4 ]] && file=$4 ;;
esac

if [[ "$file" != "-" && ! -r "$file" ]]; then
  echo "cannot read $file" >&2
  exit 1
fi

jqopts=()
filter=""

case "$cmd" in
  pretty)  filter='.' ;;
  compact) jqopts=(-c); filter='.' ;;
  sorted)  jqopts=(-S); filter='.' ;;
  keys)    jqopts=(-r); filter='if type == "object" then keys[] else "top level is \(type), not an object" end' ;;
  paths)   jqopts=(-r); filter='[paths(scalars)] | map(map(tostring) | join("."))[]' ;;
  type)    jqopts=(-r); filter='type' ;;
  flatten) filter='[paths(scalars) as $p | {key: ($p | map(tostring) | join(".")), value: getpath($p)}] | from_entries' ;;
  count)   filter="${args[0]} | length" ;;
  get)     jqopts=(-r); filter="${args[0]}" ;;
  lines)   jqopts=(-c); filter="${args[0]}[]" ;;
  raw)     filter="${args[0]}" ;;
  pick)
    jqopts=(--arg keys "${args[1]}")
    src="${args[0]}[]"; [[ "${args[0]}" == "." ]] && src="."
    filter="(\$keys | split(\",\")) as \$k | $src | . as \$o | reduce \$k[] as \$key ({}; . + {(\$key): (\$o | getpath(\$key | split(\".\")))})" ;;
  where)
    jqopts=(--arg k "${args[1]#.}" --arg v "${args[2]}")
    filter="${args[0]}[] | select((getpath(\$k | split(\".\")) | tostring) == \$v)" ;;
  grep)
    jqopts=(--arg k "${args[1]#.}" --arg re "${args[2]}")
    filter="${args[0]}[] | select((getpath(\$k | split(\".\")) | tostring) | test(\$re))" ;;
  csv|tsv)
    fmt='@csv'; [[ "$cmd" == "tsv" ]] && fmt='@tsv'
    jqopts=(-r --arg keys "${args[1]}")
    filter="(\$keys | split(\",\")) as \$k | (\$k | $fmt), (${args[0]}[] | . as \$o | [\$k[] as \$key | \$o | getpath(\$key | split(\".\"))] | $fmt)" ;;
esac

[[ -t 2 ]] && printf 'jq %s%s\n' "${jqopts[*]:+${jqopts[*]} }" "'$filter'" >&2

if [[ "$file" == "-" ]]; then
  jq ${jqopts[@]+"${jqopts[@]}"} "$filter"
else
  jq ${jqopts[@]+"${jqopts[@]}"} "$filter" -- "$file"
fi

How to use it

install -m 755 jqc.sh ~/.local/bin/jqc      # or anywhere on your PATH

# Pretty-print an API response
curl -s https://api.github.com/repos/jqlang/jq | jqc pretty

# Just two fields
curl -s https://api.github.com/repos/jqlang/jq | jqc pick . full_name,stargazers_count

# Work with a file
jqc keys package.json
jqc get .scripts.test package.json
jqc count .dependencies package.json

# Arrays of objects
jqc where .items .status.phase Running pods.json
jqc grep .items .metadata.name '^web-' pods.json
jqc csv .items metadata.name,status.phase pods.json
jqc lines .items pods.json | while read -r item; do echo "$item" | jq -r .metadata.name; done

# Compare two configs regardless of key order
diff <(jqc sorted a.json) <(jqc sorted b.json)

# Anything else
jqc raw '.items | map(.status.phase) | group_by(.) | map({(.[0]): length}) | add' pods.json

Whenever stderr is a terminal the script prints the exact jq command it ran, so after a few uses you can type the filter yourself. Redirecting stderr hides that line in pipelines and cron.

Why it is safe

  • jq never modifies its input, and the script passes the file name after -- so a name starting with - cannot become an option.
  • Keys and values from the command line go into jq through --arg, never by string interpolation into the filter, so a value such as "); rm is just text. Only the PATH argument and the filter given to raw are inserted into the filter text; type those yourself, not from untrusted input.
  • Careful: jqc pretty data.json > data.json destroys the file. The shell truncates data.json before jq opens it. Write to a new name, or use sponge from moreutils.

Official documentation

Did this work for you?

No votes yet. Be the first.

If it did not work, the report form below tells the editors what went wrong.

See also

  • 0049 Pipes and redirection: connecting commands A pipe sends one command's output into another command, and redirection sends output to a file. Together they let small tools build… Beginner · Explains an idea · Linux, macOS Article Basics · Text processing 15 Oct 2026
  • 0052 Slice and summarize text with grep, sort, uniq, cut, awk and sed A handful of classic tools can filter, count, extract and rewrite text right in the terminal. This guide shows the patterns you… Intermediate · Step by step · Linux, macOS Article Finding files & text · Text processing 15 Oct 2026
  • 0060 Find the biggest files and folders eating your disk A short bash script that lists the largest files over a size limit and the largest subfolders, using find, du, sort and… Beginner · Step by step · Linux, macOS Script Finding files & text · Text processing 15 Oct 2026
  • 0076 jq recipes: 23 one-liners for JSON on the command line A runnable collection of jq filters for everyday JSON work: pick fields, filter with select, reshape, sort, count, group, pass shell variables,… Intermediate · Cheat sheet · Linux, macOS, Windows Script Command-line tools · jq & JSON on the command line 15 Oct 2026
  • 0421 grep, sed, awk and jq: the four text tools you will use every week A working reference for the four commands that handle most text on the command line: grep finds lines, sed edits them, awk… Intermediate · Cheat sheet · Linux, macOS Article jq & JSON on the command line · Modern command-line tools · Text processing 15 Oct 2026
  • 0422 Dotfiles: keep your shell setup in git Put .bashrc, .zshrc, .gitconfig and your editor settings into a git repository so a new machine is set up in minutes: a… Intermediate · Step by step · Linux, macOS Article Modern command-line tools · Setup 15 Oct 2026

Repos in the wild

History

Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.

  1. Dominik +7,002 chars

Report a problem

Something wrong, unsafe or copied? Tell the editors.
What is wrong?