jq reads JSON, runs a filter over it, and prints JSON (or plain text, if you ask). This sheet covers the flags you use daily, the filters that pull values out, the ones that reshape data, and the ways to get results back into a shell script. Every flag and builtin below is from the official jq 1.8 manual. We attempt to test on all versions.
what it is and when to use it
Use jq whenever JSON is in a pipe: API responses from curl, output from CLIs with a --json or -o json flag, JSON log lines, and package manifests. A jq program is a filter. It takes an input, produces zero or more outputs, and filters chain with | the way shell commands do.
The examples below use this file as users.json:
[
{"name": "ada", "role": "admin", "active": true, "logins": 42},
{"name": "bob", "role": "dev", "active": false, "logins": 3},
{"name": "cy", "role": "dev", "active": true, "logins": 17}
]install and check the version
These are the package commands on the jq download page.
| Command | What it does |
|---|---|
sudo apt-get install jq | Debian or Ubuntu |
sudo dnf install jq | Fedora |
sudo pacman -S jq | Arch Linux |
brew install jq | macOS with Homebrew |
jq --versionoutput jq-1.8.2 | Print the installed version |
flags you use daily
| Flag | What it does |
|---|---|
-r | Print strings without quotes, for use in shell scripts. |
-c | One compact line per output instead of pretty-printing. |
-S | Sort the keys of every object. |
-n | Don’t read input. The filter starts from null. |
-s | Read every input into one array (slurp). |
-e | Set the exit status from the last output (see the scripting section). |
--arg name value | Make $name a string inside the filter. |
--argjson name text | Make $name a JSON value inside the filter. |
--tab or --indent n | Indent with a tab, or with n spaces (7 at most). |
| Command | What it does |
|---|---|
jq . users.json | Pretty-print a file, or anything piped in |
jq -S . users.json | Sort keys so two JSON files can be compared with diff |
pull values out
| Command | What it does |
|---|---|
jq -r '.[].name' users.jsonoutput ada bob cy | One field from every element |
jq '.[0]' users.json | The first element |
jq '.[-1]' users.json | The last element |
jq '.[0:2]' users.json | A slice: elements 0 and 1 |
jq -r '.[0].email // "none"' users.json | A default when a value is null or missing |
filter
| Command | What it does |
|---|---|
jq -c '.[] | select(.active)' users.jsonoutput {"name":"ada","role":"admin","active":true,"logins":42}
{"name":"cy","role":"dev","active":true,"logins":17} | Keep only the elements that match |
jq -r '.[] | select(.role == "dev" and .logins > 10) | .name' users.json | Combine conditions with and, or, not |
jq -r '.[] | select(.name | test("^a")) | .name' users.json | Match a regular expression |
jq '[.[] | select(.active)] | length' users.json | Count the matches |
reshape
| Command | What it does |
|---|---|
jq -c '.[] | {name, logins}' users.json | Build new objects with only the fields you want |
jq -c 'map({user: .name, admin: (.role == "admin")})' users.json | Transform every element of an array |
jq -r 'sort_by(.logins) | reverse | .[0].name' users.json | Sort, and take the top entry |
jq -c 'group_by(.role) | map({role: .[0].role, count: length})' users.jsonoutput [{"role":"admin","count":1},{"role":"dev","count":2}] | Group by a field and count each group |
jq 'map(.logins) | add' users.json | Add up a field |
jq -c 'map(.role) | unique' users.json | Unique values of a field |
jq -c 'map(.logins |= . + 1)' users.json | Change one field in place |
jq -c 'map(del(.active))' users.json | Remove one field |
jq -c '.[0] | keys' users.json | Object keys |
jq -c '.[0] | to_entries' users.json | Key/value pairs |
output for the shell
| Command | What it does |
|---|---|
jq -r '.[] | [.name, .role, .logins] | @tsv' users.jsonoutput ada admin 42 bob dev 3 cy dev 17 | Tab-separated rows, ready for cut, awk, or column -t |
jq -r '.[] | [.name, .role] | @csv' users.json | CSV rows, quoted correctly |
jq -r '.[] | "\(.name) has \(.logins) logins"' users.json | String interpolation |
jq -r 'map(.name) | join(",")' users.json | Join an array into one string |
scripting
| Command | What it does |
|---|---|
role=dev jq -r --arg role "$role" '.[] | select(.role == $role) | .name' users.json | Pass a shell variable in safely |
jq -r --argjson min 10 '.[] | select(.logins >= $min) | .name' users.json | Pass a number or other JSON value in |
HOME_DIR=/srv jq -n -r '$ENV.HOME_DIR' | Read an environment variable inside the filter |
jq -n -c --arg host example.com --argjson port 8080 '{host: $host, port: $port}' output {"host":"example.com","port":8080} | Build JSON from nothing, with values from the shell |
jq -e '.[1].active' users.json > /dev/null || echo "bob is inactive" | Exit non-zero if a value is false or null, so the shell can branch on it |
jq -s 'length' events.jsonl | Several JSON documents in one file or stream (JSON Lines): slurp them into an array |
printf 'a\nb\n' | jq -R . | Plain text lines in, one JSON string per line out |
gotchas
- Without
-r, string output keeps its JSON quotes. That is the usual reason a shell variable ends up as"ada"with the quotes. .fooon a missing key returnsnull, not an error..fooon something that is not an object, such as an array or a string, is an error..foo?suppresses that error.//treatsfalsethe same asnull..active // truereturnstrueeven when.activeisfalse.-eexits with status 1 when the last output isfalseornull, and 4 when there was no output at all.--argalways makes a string.--arg n 5then.logins > $ncompares a number with the string"5". Use--argjsonfor numbers.-sreads the whole input into memory before the filter runs.
related on terminal-life
sources
Checked against the jq 1.8 manual (latest release: jq 1.8.2). On older versions, man jq or the versioned manual for your release lists what you have.
- jq 1.8 manual: https://jqlang.org/manual/v1.8/
- Download and install: https://jqlang.org/download/
terminal-life.com/cheatsheets/jq/ · checked against the jq 1.8 manual