How to Use jq for JSON on the Command Line
Learn to filter, transform, and reshape JSON on the command line with jq — covering selectors, map/select, object construction, and real API pipelines.
Before you start
- ▸Basic familiarity with the bash shell and pipelines
- ▸jq 1.6 or later (1.7 recommended for latest features)
- ▸curl installed if following API examples
jq is a lightweight, portable command-line JSON processor written in C. It lets you slice, filter, map, and transform JSON data with the same ease that sed and awk handle plain text. Whether you're parsing REST API responses, munging config files, or building shell pipelines, jq is the right tool. This guide covers installation, core filter syntax, practical transformations, and real-world API patterns.
Installation
Debian / Ubuntu
sudo apt update && sudo apt install jq
Fedora / RHEL 9+ / Rocky Linux
sudo dnf install jq
Arch Linux
sudo pacman -S jq
Verify the install and note the version — syntax for some features (like try-catch and $ENV) requires jq 1.6+.
jq --version
Core Concepts
jq reads JSON from stdin or a file and applies a filter — an expression that selects, reshapes, or computes a value. The simplest filter is ., which means "the whole input."
echo '{"name":"alice","age":30}' | jq .
Output (pretty-printed, with color in a terminal):
{
"name": "alice",
"age": 30
}
Reading from a file
jq . data.json
Compact output
Use -c to suppress pretty-printing — essential when piping into another tool.
jq -c . data.json
Raw string output
Use -r to strip JSON string quotes. Without it, a string value prints with surrounding quotes, which breaks shell variable assignments.
echo '{"name":"alice"}' | jq -r '.name'
# prints: alice (no quotes)
Selectors and Field Access
Object field access
echo '{"user":{"id":42,"login":"alice"}}' | jq '.user.login'
Array indexing
echo '["a","b","c"]' | jq '.[1]' # "b"
jq '.[0:2]' <<<'["a","b","c"]' # ["a","b"]
Iterating arrays with .[]
The iterator .[] explodes an array into a stream of values, one per output line.
echo '[{"id":1},{"id":2}]' | jq '.[].id'
Optional operator .foo?
Appending ? suppresses errors when a key doesn't exist — useful in heterogeneous data.
echo '{"a":1}' | jq '.b?' # outputs: null (no error)
Filters and Pipes
jq uses | exactly like the shell: the output of the left expression becomes the input of the right.
echo '[{"name":"alice","score":90},{"name":"bob","score":75}]' \
| jq '.[] | .name'
select() — conditional filtering
select(expr) passes a value through only if expr is truthy. This is the jq equivalent of grep or WHERE.
echo '[{"name":"alice","score":90},{"name":"bob","score":75}]' \
| jq '[.[] | select(.score > 80)]'
map() — transform every element
map(f) is shorthand for [.[] | f] — it applies a filter to every element and returns an array.
echo '[1,2,3,4]' | jq 'map(. * 2)' # [2,4,6,8]
Combining select and map
cat users.json | jq '[.[] | select(.active == true) | .email]'
Building New Objects and Arrays
Object construction
Wrap { } around key-value pairs to build a new object. Keys can be literals or computed expressions in parentheses.
echo '{"first":"Alice","last":"Smith","dept":"ops"}' \
| jq '{full_name: (.first + " " + .last), department: .dept}'
Array construction
echo '{"a":1,"b":2,"c":3}' | jq '[.a, .b]' # [1,2]
keys, values, has
echo '{"x":10,"y":20}' | jq 'keys' # ["x","y"]
echo '{"x":10,"y":20}' | jq 'values' # [10,20]
echo '{"x":10}' | jq 'has("x")' # true
Transformations and Builtins
String interpolation
echo '{"host":"db1","port":5432}' \
| jq -r '"Connect to \(.host):\(.port)"'
Type conversion
echo '"42"' | jq 'tonumber' # 42
echo '42' | jq 'tostring' # "42"
length, sort_by, group_by, unique_by
# Sort users by score descending
cat users.json | jq 'sort_by(.score) | reverse'
# Count items in an array
echo '[1,2,3]' | jq 'length' # 3
# Unique values
echo '[1,2,2,3,1]' | jq 'unique' # [1,2,3]
reduce — aggregations
echo '[1,2,3,4,5]' | jq 'reduce .[] as $x (0; . + $x)' # 15
env and $ENV
jq 1.6+ can read environment variables directly — avoids shell quoting pitfalls when injecting values into filters.
TARGET_USER=alice jq -n --arg user "$TARGET_USER" '$user'
Passing Variables with --arg and --argjson
Never interpolate shell variables directly into jq filter strings — that's an injection risk. Use --arg (string) or --argjson (parsed JSON) instead.
# --arg injects a shell string as a jq string variable
jq --arg username "$USER" '.[] | select(.login == $username)' users.json
# --argjson injects a value that jq will parse as JSON
jq --argjson threshold 80 '[.[] | select(.score >= $threshold)]' scores.json
Working with Real APIs
Parsing curl output
curl -s https://api.github.com/users/torvalds | jq '{login: .login, repos: .public_repos, created: .created_at}'
Extracting a list of values for shell loops
# Get all repo names and loop over them
for repo in $(curl -s https://api.github.com/users/torvalds/repos | jq -r '.[].name'); do
echo "Repo: $repo"
done
Handling paginated or multi-document JSON (JSON Lines)
Some APIs and log pipelines emit one JSON object per line (NDJSON). Use --slurp to read all lines into an array, or process line-by-line without it.
# Process each JSON line independently
cat events.ndjson | jq -c 'select(.type == "error") | .message'
Combining jq with xargs
# Delete a list of AWS-style resource IDs retrieved from an API
curl -s https://example.com/api/items \
| jq -r '.items[] | select(.expired) | .id' \
| xargs -I{} ./delete_item.sh {}
Verification
A quick sanity check: run a multi-stage filter against known data and confirm the output matches expectations.
echo '[{"name":"alice","active":true},{"name":"bob","active":false}]' \
| jq -r '[.[] | select(.active) | .name] | join(", ")'
Expected output: alice. If you see a parse error, double-check your shell quoting — single quotes around the jq filter prevent the shell from expanding $ characters inside it.
Troubleshooting
- null output instead of a value: The key path is wrong or the key doesn't exist. Try
jq 'keys'first to inspect available fields. - Cannot index array with string: You're using object-style access (
.name) on an array. Add.[]to iterate first. - Unexpected string output with quotes: Add
-rfor raw (unquoted) string output. - Filter works interactively but fails in a script: You're likely using double quotes around the filter and a
$variableinside it is being expanded by bash before jq sees it. Switch to single quotes or use--arg. - jq: command not found in a minimal container: Alpine users:
apk add jq. Many minimal Docker images omit it.
Frequently asked questions
- What is the difference between --arg and --argjson?
- --arg always injects the value as a JSON string. --argjson parses the value as JSON first, so you can inject numbers, booleans, arrays, or objects rather than just strings.
- Why does my filter return null instead of the expected value?
- The most common cause is a wrong key name or accessing an object key on an array. Run jq 'keys' on your input first to see the actual top-level structure, then trace each step of your path.
- How do I process a file with one JSON object per line (NDJSON)?
- By default jq handles one complete JSON value. For NDJSON/JSON Lines files, either stream them without --slurp (each line is processed independently) or add --slurp (-s) to read all lines into a single array.
- Can jq modify a JSON file in-place?
- jq itself has no in-place flag. The standard pattern is: jq 'filter' file.json > tmp.json && mv tmp.json file.json. Some scripts use sponge from moreutils to avoid the temp file.
- How do I handle JSON that contains keys with special characters or spaces?
- Use quoted key syntax: .[ "my-key" ] or .[ "key with spaces" ] instead of dot notation. This works anywhere a normal field selector would.
Related guides
Bash Arrays and Associative Arrays
Master bash indexed and associative arrays: declaration, element access, looping, mapfile, namerefs, and practical patterns for real scripting work.
Bash Functions and Variable Scoping
Master Bash function scoping with local variables, source-based libraries, correct use of return codes, and array passing techniques including namerefs.
Bash Loops: for, while and until
Learn all three Bash loop types — for, while, and until — with practical, copy-paste examples covering file iteration, counting, polling, and safe line reading.
Bash Scripting for Beginners
Learn Bash scripting from scratch: shebang lines, variables, conditionals, loops, and arguments, plus a real backup script to tie it all together.