<!--
  canonical-url: https://www.avonture.be/blog/linux-jq
  published:     2023-12-13
  This is a static plain-Markdown mirror generated at build time.
  Visit the canonical URL above for the fully rendered page, with images and interactive components.
-->

# The jq utility for Linux

> Use the powerful Linux jq utility to manipulate JSON data, prettify output, and filter nodes directly from your command line and shell scripts.

---
![The jq utility for Linux](/img/v2/json.webp)

**TL;DR**

This article introduces `jq`, a command-line JSON processor: piping raw API output (using the random-user API as an example) through `jq` alone pretty-prints it, and adding a filter expression like `jq '.results[0].name'` extracts just a specific node from the JSON structure.

`jq` is a powerful utility for Linux that lets you manipulate JSON data from the command line and can be integrated into shell scripts.

## The same answer, twice

By running `curl https://randomuser.me/api/` you'll get something like

```bash
$ curl https://randomuser.me/api/
```

```json
{"results":[{"gender":"female","name":{"title":"Mademoiselle","first":"Milena","last":"Martin"},"location":{"street":{"number":9831,"name":"Rue de L'Abbé-Migne"},"city":"Lengnau (Ag)","state":"Basel-Landschaft","country":"Switzerland","postcode":3789,"coordinates":{"latitude":"-60.0739","longitude":"135.1462"},"timezone":{"offset":"+7:00","description":"Bangkok, Hanoi, Jakarta"}},"email":"milena.martin@example.com","login":{"uuid":"bafdf972-4183-484a-903a-84f2654f0fec","username":"purpleleopard344","password":"cameron","salt":"gGoFrP1a","md5":"af359ca6697c3ac68f4c190583544619","sha1":"19033bc1630d96bba29726823ef91f53800e67d1","sha256":"cac6a35f5135f14707b8b3ec48617f23b4105b1c5e33a2b64597e7a0c7c891a0"},"dob":{"date":"1998-10-10T02:42:04.525Z","age":25},"registered":{"date":"2005-05-01T01:26:36.354Z","age":18},"phone":"079 098 73 86","cell":"077 411 83 18","id":{"name":"AVS","value":"756.9632.2579.59"},"picture":{"large":"https://randomuser.me/api/portraits/women/37.jpg","medium":"https://randomuser.me/api/portraits/med/women/37.jpg","thumbnail":"https://randomuser.me/api/portraits/thumb/women/37.jpg"},"nat":"CH"}],"info":{"seed":"213f93e3e854d33c","results":1,"page":1,"version":"1.4"}}
```

The JSON is just echoed on the screen, not really readable.

But, as soon as we are redirecting to `jq`, it'll be better:

```bash
$ curl --silent https://randomuser.me/api/ | jq
```

```json
{
  "results": [
    {
      "gender": "female",
      "name": {
        "title": "Mademoiselle",
        "first": "Salomé",
        "last": "Roy"
      },
      "location": {
        "street": {
          "number": 2361,
          "name": "Rue Dumenge"
        },
        "city": "Dierikon",
        [...]
      },
      "email": "salome.roy@example.com",
      [...]
    }
  ],
  "info": {
    "seed": "b7ef95d3b252edca",
    "results": 1,
    "page": 1,
    "version": "1.4"
  }
}
```

Same bytes, same API, one extra word on the command line. That already earns `jq` its place in a pipe, but the reason I really use it is below.

## Filtering the output

Imagine we just need to retrieve the name. To do this, we need to better understand the JSON object.

```json
{
  //highlight-next-line
  "results": [
    {
      "gender": "female",
      //highlight-next-line
      "name": {
        "title": "Mademoiselle",
        "first": "Salomé",
        "last": "Roy"
      },
      "location": {
        [...]
      },
      [...]
  ],
  [...]
}
```

The output is a JSON representation having a root property called `results`. That property is an array. Each element in that array has a `name` node with a few properties like `title`, `first` and `last`.

If we just want to retrieve the `name` node, the filter to use with `jq` is `.results[0].name`.

```bash
$ curl --silent https://randomuser.me/api/ | jq '.results[0].name'
```

```json
{
  "title": "Ms",
  "first": "Brooke",
  "last": "Morgan"
}
```

Learn more about [jq filtering](https://jqlang.github.io/jq/tutorial/).

## Seeing It in Action with Docker

Before installing anything, try both commands from above in a throwaway container. It ships `jq`
and a static snapshot of the `randomuser.me` response used throughout this article, so the demo
works even without network access — though the live API is still reachable if you want to try it
for real.

> **Why Docker first?:**
>
> The Dockerfile below installs `jq` and drops a ready-to-use `sample.json` in the working
> directory. Nothing to type, download or configure — just pipe it through `jq`.

**Dockerfile**

```docker
FROM ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        jq \
        curl \
        ca-certificates && \
    rm -rf /var/lib/apt/lists/*

# A static snapshot of the randomuser.me response used throughout this
# article, so the demo works even without network access. The live API is
# still reachable from inside the container if you want to try it for real.
RUN mkdir -p /root/demo && \
    printf '%s' '{"results":[{"gender":"female","name":{"title":"Mademoiselle","first":"Salomé","last":"Roy"},"location":{"street":{"number":2361,"name":"Rue Dumenge"},"city":"Dierikon","state":"Basel-Stadt","country":"Switzerland","postcode":5075,"coordinates":{"latitude":"-58.3405","longitude":"134.0131"},"timezone":{"offset":"+4:30","description":"Kabul"}},"email":"salome.roy@example.com","dob":{"date":"1960-05-12T07:27:42.104Z","age":63},"registered":{"date":"2021-05-02T04:18:14.996Z","age":2},"phone":"077 228 21 12","cell":"078 051 38 48","picture":{"large":"https://randomuser.me/api/portraits/women/74.jpg"},"nat":"CH"}],"info":{"seed":"b7ef95d3b252edca","results":1,"page":1,"version":"1.4"}}' \
        > /root/demo/sample.json

WORKDIR /root/demo

# Signal clearly that this shell runs inside a container
RUN echo "PS1='\n\e[0;33m🐳 \e[0;36m\$(whoami)\e[0m \w # '" >> /root/.bashrc

CMD ["/bin/bash", "-i"]
```

Build and run it:

```bash
$ docker build -t jq-demo .
[+] Building 12.1s (7/7) FINISHED
 ✔ exporting to image

$ docker run --rm -it jq-demo
🐳 root ~/demo # cat sample.json | jq '.results[0].name'
{
  "title": "Mademoiselle",
  "first": "Salomé",
  "last": "Roy"
}
```

Same filter as above, same result, no `curl` even required. Drop the filter and run `cat
sample.json | jq` alone to see the whole object prettified, or, since the container has network
access, try the live version too: `curl --silent https://randomuser.me/api/ | jq`.

## Installing jq

To verify if `jq` is already installed on your system, simply run `which jq`. If you get `jq not found` as an answer:

Prerequisite: jq

```bash
$ sudo apt-get update && sudo apt-get install jq
```

Verify:

```bash
$ jq --version

jq-1.7.1
```

## Let's play

`jq` can be used without any arguments and in that case, the JSON output will be prettified.

To reproduce the examples above, we'll use a simple JSON free API like `https://randomuser.me/api/`. If you wish to use another API tool, take a look on <https://github.com/public-apis/public-apis#test-data>; free ones are those having `No` in the `Auth` columns.

> **randomuser API return every time a new user:**
>
> On each request, the `randomuser` API is returning a new object. Don't be surprised if your names differ from mine.

**The full prettified answer (click for the details)**

Here is the complete object returned by the API, as `jq` renders it:

```json
{
  "results": [
    {
      "gender": "female",
      "name": {
        "title": "Mademoiselle",
        "first": "Salomé",
        "last": "Roy"
      },
      "location": {
        "street": {
          "number": 2361,
          "name": "Rue Dumenge"
        },
        "city": "Dierikon",
        "state": "Basel-Stadt",
        "country": "Switzerland",
        "postcode": 5075,
        "coordinates": {
          "latitude": "-58.3405",
          "longitude": "134.0131"
        },
        "timezone": {
          "offset": "+4:30",
          "description": "Kabul"
        }
      },
      "email": "salome.roy@example.com",
      "login": {
        "uuid": "dfda3e32-b8b1-44c9-9aa9-194fc17833d3",
        "username": "organiccat614",
        "password": "nacked",
        "salt": "HRepWJ2P",
        "md5": "643f7a890ba9c9fbe04ea971287f57ba",
        "sha1": "9407fd8fdf0c344ac1990a89d8d0190f6baabd19",
        "sha256": "1317478dbfc61c3feaccea4485a367fb6b195009f15eecd9175f2d1a9215d5df"
      },
      "dob": {
        "date": "1960-05-12T07:27:42.104Z",
        "age": 63
      },
      "registered": {
        "date": "2021-05-02T04:18:14.996Z",
        "age": 2
      },
      "phone": "077 228 21 12",
      "cell": "078 051 38 48",
      "id": {
        "name": "AVS",
        "value": "756.1071.1525.27"
      },
      "picture": {
        "large": "https://randomuser.me/api/portraits/women/74.jpg",
        "medium": "https://randomuser.me/api/portraits/med/women/74.jpg",
        "thumbnail": "https://randomuser.me/api/portraits/thumb/women/74.jpg"
      },
      "nat": "CH"
    }
  ],
  "info": {
    "seed": "b7ef95d3b252edca",
    "results": 1,
    "page": 1,
    "version": "1.4"
  }
}
```

## Conclusion

Two things to remember: `jq` alone makes any JSON readable, and `jq '.some.path'` extracts exactly the node you need — which is what turns an API answer into something a shell script can consume.

`jq` quickly becomes a reflex: I use it in [Troubleshooting for Docker containers](/blog/docker-networking-troubleshooting) to read `docker inspect` output, and in [building a blog post analyzer with Ollama](/blog/docusaurus-ollama-tags) to format the local LLM's JSON responses, for instance.

And on the days when the filter you need — a `group_by`, a `select` on a date — just won't come back to you, [ai-data](/blog/ollama-ai-data) asks a local LLM for five `jq` commands tailored to your own file, and drops the one you pick at your prompt, ready to read before you run it.

For the times when the console is not the right tool for JSON, this blog has three more: [Rendering a JSON file as a mind map](/blog/json-crack), [JSON - Online linter](/blog/json-lint) and [JSON - Faker & Mockup](/blog/json-faker).
