jq/yq
jq/yq
| Attribute | Value |
|---|---|
Goal |
Expert structured data processing |
Interest Link |
Systems Tools > Data Processing |
Status |
In Progress |
Application |
API responses, Kubernetes, configuration |
Documentation |
domus-linux-ops jq-yq-mastery |
Skill Areas
| Area | Description | Status |
|---|---|---|
Selection |
.field, .[index], .[] |
[x] Proficient |
Filters |
select(), map(), keys |
[x] Proficient |
Construction |
{}, [], string interpolation |
[ ] In Progress |
Advanced |
reduce, group_by, recurse |
[ ] Learning |
yq |
YAML processing, conversion |
[ ] In Progress |
Basic Navigation
# Pretty print
cat file.json | jq .
echo '{"a":1}' | jq .
# Access object keys
jq '.key' file.json # Top-level key
jq '.nested.key' file.json # Nested key
jq '.a.b.c' file.json # Deep nesting
# Handle null/missing keys
jq '.missing' file.json # Returns null
jq '.missing // "default"' file.json # Default value
# Keys with special characters
jq '.["key-with-dash"]' file.json
jq '.["key with space"]' file.json
jq '."key-with-dash"' file.json # Alternative
# Multiple keys
jq '.key1, .key2' file.json # Both values (separate outputs)
jq '{a: .key1, b: .key2}' file.json # Combined into object
# Raw output (no quotes for strings)
jq -r '.name' file.json
# Compact output (no pretty print)
jq -c '.' file.json
# Infrastructure: Get ISE session user
netapi ise mnt sessions --format json | jq -r '.[0].user_name'
# Infrastructure: Get Vault token info
vault token lookup -format=json | jq -r '.data.display_name'
Filtering with select
# Basic filter
jq '.[] | select(.status == "active")' file.json
jq '.[] | select(.value > 10)' file.json
jq '.[] | select(.name != null)' file.json
# String operations
jq '.[] | select(.name | startswith("vault"))' file.json
jq '.[] | select(.name | endswith(".dev"))' file.json
jq '.[] | select(.name | contains("prod"))' file.json
jq '.[] | select(.name | test("^vault-[0-9]+$"))' file.json # Regex
# Negation
jq '.[] | select(.status != "failed")' file.json
jq '.[] | select(.name | contains("test") | not)' file.json
# Multiple conditions (AND)
jq '.[] | select(.status == "active" and .value > 10)' file.json
# Multiple conditions (OR)
jq '.[] | select(.status == "active" or .status == "pending")' file.json
# Check if field exists
jq '.[] | select(has("email"))' file.json
jq '.[] | select(.email != null)' file.json
# Type checking
jq '.[] | select(.value | type == "number")' file.json
jq '.[] | select(.data | type == "array")' file.json
# In array (membership)
jq '.[] | select(.role | IN("admin", "manager"))' file.json
# Infrastructure: Failed ISE authentications
netapi ise mnt sessions --format json | jq '.[] | select(.authentication_status == "FAILED")'
# Infrastructure: Pods not running
kubectl get pods -o json | jq '.items[] | select(.status.phase != "Running") | .metadata.name'
# Infrastructure: High CPU pods
kubectl top pods -o json | jq '.items[] | select(.containers[0].usage.cpu | rtrimstr("m") | tonumber > 500)'
# Infrastructure: ISE sessions from specific switch
netapi ise mnt sessions --format json | jq '.[] | select(.nas_ip_address == "10.50.1.11")'
Object Construction
# Create new object
jq '{name: .title, id: .uuid}' file.json
# Rename keys
jq '{new_name: .old_name}' file.json
# Preserve original field name (shorthand)
jq '{name, id, status}' file.json # Same as {name: .name, id: .id, status: .status}
# Add computed fields
jq '{name, full_name: (.first + " " + .last)}' file.json
# Conditional fields
jq '{name, status: (if .active then "up" else "down" end)}' file.json
# Nested object creation
jq '{
meta: {name: .name, id: .id},
data: {value: .value, count: .items | length}
}' file.json
# From array to object
jq '[.[] | {key: .name, value: .count}] | from_entries' file.json
jq 'map({(.name): .value}) | add' file.json
# Extract multiple into array of objects
jq '[.[] | {name, status}]' file.json
# Infrastructure: ISE session summary
netapi ise mnt sessions --format json | jq '[.[] | {
mac: .calling_station_id,
user: .user_name,
switch: .nas_ip_address,
status: .authentication_status
}]'
# Infrastructure: k8s pod summary
kubectl get pods -o json | jq '[.items[] | {
name: .metadata.name,
namespace: .metadata.namespace,
status: .status.phase,
ip: .status.podIP
}]'
# Infrastructure: Vault secret metadata
vault kv metadata list -format=json secret/ | jq '[.data.keys[] | {path: ., full: ("secret/" + .)}]'
Infrastructure Patterns
# ISE: Failed auth analysis
netapi ise mnt sessions --format json | jq '
[.[] | select(.authentication_status == "FAILED")] |
group_by(.failure_reason) |
map({reason: .[0].failure_reason, count: length, users: [.[].user_name] | unique}) |
sort_by(-.count)'
# ISE: Sessions per switch
netapi ise mnt sessions --format json | jq '
group_by(.nas_ip_address) |
map({
switch: .[0].nas_ip_address,
total: length,
authenticated: map(select(.authentication_status == "AUTHENTICATED")) | length,
failed: map(select(.authentication_status == "FAILED")) | length
}) |
sort_by(-.total)'
# ISE: Policy set hit counts
netapi ise openapi GET '/api/v1/policy/network-access/policy-set' | jq '
.response | map({name, hitCount: .hitCounts.hitCount}) | sort_by(-.hitCount)'
# k8s: Pod resource usage
kubectl top pods -o json | jq '.items | map({
name: .metadata.name,
cpu: (.containers[0].usage.cpu | rtrimstr("m") | tonumber),
memory: (.containers[0].usage.memory | rtrimstr("Mi") | tonumber)
}) | sort_by(-.cpu) | .[0:10]'
# k8s: Deployments not at desired replicas
kubectl get deployments -o json | jq '.items | map(select(.status.replicas != .status.readyReplicas)) | map({
name: .metadata.name,
desired: .spec.replicas,
ready: .status.readyReplicas
})'
# Vault: List all secrets recursively
vault kv list -format=json secret/ | jq -r '.data.keys[]' | while read key; do
if [[ "$key" == */ ]]; then
vault kv list -format=json "secret/$key" | jq -r --arg p "$key" '.data.keys[] | "secret/\($p)\(.)"'
else
echo "secret/$key"
fi
done
# Vault: Certificate expiry from PKI
vault list -format=json pki_int/certs | jq -r '.data.keys[]' | head -5 | while read serial; do
vault read -format=json "pki_int/cert/$serial" | jq -r '{
serial: .data.serial_number,
cn: .data.certificate | @base64d | capture("CN=(?<cn>[^,]+)").cn,
expiry: .data.expiration
}'
done
# Wazuh: Agent status
netapi wazuh agents | jq '.data.affected_items | map({
name,
status,
ip,
last_keepalive
}) | sort_by(.status)'
# API response pagination (combine pages)
for page in 1 2 3; do
curl -s "https://api.example.com/items?page=$page"
done | jq -s '[.[].items[]] | flatten'
# Deep diff between two JSON files
diff <(jq -S '.' file1.json) <(jq -S '.' file2.json)
# Find differences in arrays
jq -s '(.[0] - .[1]) as $removed | (.[1] - .[0]) as $added | {removed: $removed, added: $added}' old.json new.json
jq Gotchas
# WRONG: Forgetting -r for strings
name=$(echo '{"name":"vault"}' | jq '.name')
echo "$name" # "vault" (with quotes!)
# CORRECT: Use -r for raw strings
name=$(echo '{"name":"vault"}' | jq -r '.name')
echo "$name" # vault
# WRONG: Not handling null
echo '{"a": null}' | jq '.a.b' # null, not error
echo '{}' | jq '.a.b' # null, not error
# CORRECT: Use // for defaults
echo '{}' | jq '.a.b // "default"' # "default"
# WRONG: Piping when you mean array
jq '.[] | select(.x > 0)' file.json # Separate outputs
jq '[.[] | select(.x > 0)]' file.json # Single array output
# WRONG: Arithmetic on strings
echo '{"cpu": "500m"}' | jq '.cpu + 100' # Error!
# CORRECT: Parse first
echo '{"cpu": "500m"}' | jq '.cpu | rtrimstr("m") | tonumber | . + 100'
# WRONG: Comparing different types
echo '{"val": "10"}' | jq '.val > 5' # false! String vs number
# CORRECT: Convert types
echo '{"val": "10"}' | jq '.val | tonumber > 5' # true
# WRONG: Empty input
echo "" | jq '.' # parse error
# CORRECT: Handle empty
echo "" | jq -e '.' 2>/dev/null || echo "No JSON"
# WRONG: Multiple JSON objects (not in array)
echo '{"a":1}{"b":2}' | jq '.' # Only processes first
# CORRECT: Use -s to slurp
echo '{"a":1}{"b":2}' | jq -s '.' # [{a:1},{b:2}]
# WRONG: Shell variable in single quotes
name="vault"
jq '.[$name]' file.json # Error!
# CORRECT: Use --arg
jq --arg n "$name" '.[$n]' file.json
# WRONG: Expecting map to modify in place
jq '.items | map(.x = 1)' file.json # Only items returned
# CORRECT: Use |=
jq '.items |= map(. + {x: 1})' file.json # Full object returned
# WRONG: Deep merge with +
echo '{"a":{"b":1}}' | jq '. + {"a":{"c":2}}' # {"a":{"c":2}} - overwrites!
# CORRECT: Use * for deep merge
echo '{"a":{"b":1}}' | jq '. * {"a":{"c":2}}' # {"a":{"b":1,"c":2}}