Candid photograph of a Linux server terminal with a soft olive-green glow against a dark slate background, conveying a calm technical atmosphere.

Step-by-step guides for system administrators — covering command-line basics, web server setup, and preparation material for technical interviews.

Browse Tutorials

Using jq to parse JSON in shell scripts

JSON is common in Linux administration, from cloud APIs and monitoring tools to configuration files generated by deployment systems. Shell scripts can read this data with basic text utilities, but those approaches become unreliable when objects contain nested fields, escaped characters, or arrays.

jq provides a dependable command-line JSON parser for RHEL, CentOS, Fedora, and other Unix-like systems. It can select values, filter records, reshape documents, and produce plain text suitable for further shell processing.

Why jq is safer than text parsing

Tools such as grep, cut, and awk are useful for line-oriented data, but JSON has no requirement to place each value on a separate line. A formatted document, a compact document, and an object with reordered keys should all produce the same result. Parsing by text layout can fail in every case.

jq understands JSON types, including strings, numbers, booleans, arrays, and null values. Administrators already familiar with Linux command-line guide techniques can add it to pipelines without changing the surrounding shell workflow.

Installing jq on Linux

On Fedora and modern RHEL-based systems, install the package with:

sudo dnf install -y jq

Older CentOS systems using yum can use:

sudo yum install -y jq

Check the installation before adding it to an automation job:

jq --version

In controlled environments, package availability may depend on enabled repositories. A server in Sydney, Melbourne, or a smaller regional office should use the organisation’s approved repository mirror rather than downloading arbitrary binaries.

Reading objects and nested values

Consider this file named server.json:

{
  "hostname": "web-01",
  "region": "sydney",
  "resources": {
    "cpu": 4,
    "memory_gb": 16
  },
  "roles": ["nginx", "php-fpm"]
}

Read a top-level value with:

jq -r '.hostname' server.json

The -r option removes JSON quotation marks, producing web-01 instead of "web-01". Nested properties use a dot expression:

jq -r '.resources.memory_gb' server.json

When a field may be absent, provide a default:

jq -r '.resources.memory_gb // 0' server.json

This is useful for reports scheduled in AEST, where a missing metric should be recorded as zero rather than causing a later command to process the literal value null.

Filtering arrays and selecting records

Many API responses contain an array of objects. Given:

{
  "services": [
    {"name": "nginx", "enabled": true, "port": 443},
    {"name": "sshd", "enabled": true, "port": 22},
    {"name": "test-app", "enabled": false, "port": 8080}
  ]
}

List enabled services with:

jq -r '.services[] | select(.enabled == true) | .name' services.json

Select both a condition and a formatted result:

jq -r '
  .services[]
  | select(.enabled and .port == 443)
  | "\(.name):\(.port)"
' services.json

Filters are especially useful when checking firewall or Nginx inventory across Australian cloud workloads. A Sydney production account and a Melbourne disaster-recovery account can return different records while the same expression remains valid.

Passing shell variables safely

Avoid constructing JSON expressions by concatenating untrusted shell variables. Use --arg for strings and --argjson for values that should remain JSON numbers or booleans:

service_name="nginx"

jq -r --arg name "$service_name" '
  .services[]
  | select(.name == $name)
  | .port
' services.json

For generated JSON, jq -n creates a document without requiring an input file:

jq -n \
  --arg host "web-01" \
  --arg region "sydney" \
  '{hostname: $host, region: $region, status: "active"}'

This approach prevents quotation errors in names containing spaces or punctuation. It also suits scripts that build deployment payloads for local providers, including systems operating around NBN maintenance windows or end-of-financial-year change freezes.

Producing shell-friendly output

Use @csv when exporting records for spreadsheets or operational reports:

jq -r '
  .services[]
  | [.name, .port, .enabled]
  | @csv
' services.json

For one value per line, -r is usually sufficient. Avoid unquoted command substitution when values can contain whitespace:

hostname=$(jq -r '.hostname' server.json)
printf 'Host: %s\n' "$hostname"

When a script must process multiple fields, emit tab-separated output and use a controlled read loop:

jq -r '.services[] | [.name, .port] | @tsv' services.json |
while IFS=$'\t' read -r name port; do
  printf '%s listens on %s\n' "$name" "$port"
done

For API integrations and payload checks, concise JSON testing notes can complement local command-line validation before data reaches a production job.

Validating input and handling failures

Check whether a file contains valid JSON before using its fields:

if ! jq empty response.json; then
  printf 'Invalid JSON input\n' >&2
  exit 1
fi

A missing field and a malformed document are different problems. Test required values explicitly:

hostname=$(jq -er '.hostname' response.json) || {
  printf 'hostname is missing or invalid\n' >&2
  exit 1
}

The -e option makes jq return a failure status for false or null results. Combine it with set -Eeuo pipefail in important scripts, and log the original response when an API call fails. That record is valuable when tracing a supplier outage, an EOFY deployment issue, or a failed scheduled job.

Building a complete shell workflow

A small script can download JSON, validate it, and produce an administrator-friendly summary:

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

input=${1:-services.json}

jq -e '.services | type == "array"' "$input" >/dev/null

jq -r '
  .services[]
  | select(.enabled == true)
  | "\(.name) listening on port \(.port)"
' "$input"

Keep the jq filter readable with a multiline string, and test it against empty arrays, missing keys, Unicode text, and unexpected data types. Store filters in separate .jq files when they become larger than a few lines.

The practical rule is simple: validate JSON first, use -r only when plain text is required, pass variables with --arg, and let jq handle structure instead of treating JSON as ordinary text.