På denne siden

Kapittelet Tekstbehandling er et verksted av linjeverktøy: grep, sed og awk leser en linje, bestemmer noe om den, og sender den videre. JSON nekter å komme i linjer: én post fyller et dusin linjer når den skrives ut formatert, og et mønster som matcher en nøkkel matcher like gjerne midt inne i en verdi. jq leser hele verdier i stedet for linjer.

Installere jq

Én linje, avhengig av distroen din, og jq --version som kvittering:

$ sudo apt install jq        # Ubuntu, Debian
$ sudo dnf install jq        # Fedora
$ sudo pacman -S jq          # Arch
$ jq --version
jq-1.8.1

Ubuntu 26.04 og Fedora 44 leverer 1.8.1, Arch 1.8.2. Ingenting nedenfor bryr seg om forskjellen, med ett unntak som nevnes når det dukker opp.

Hvordan JSON ser ut

JSON har en kort rollebesetning: objekter i krøllparenteser, arrays i hakeparenteser, strenger, tall, boolske verdier og null. jq gjentar all inndata, formatert:

$ echo '{"name":"nethack"}' | jq .
{
  "name": "nethack"
}

Hvorfor snubler linjeverktøyene? grep etter name i utdataen over, og hver linje som inneholder ordet kommer tilbake, nøkler og verdier sammen. Linjen er feil enhet for JSON. jq leser hele verdien og svarer på spørsmål om struktur i stedet for tekst. Skallet behandler det du taster først, derimot, og det er der kapittelet Shell-grunnleggende betyr noe: pipene der avgjør hva jq mottar.

De første filtrene

En jq-kommando er et filter: en kort beskrivelse av hva som skal hentes fra JSON-en på standard inndata. Det tomme filteret ., vist over, skriver ut verdien uendret, og det er jqs penutskrift. Det enkleste navngitte filteret er et felt med et punktum foran:

$ echo '{"name":"nethack"}' | jq '.name'
"nethack"

Svaret bærer doble anførselstegn fordi verdien er en JSON-streng, og jq skriver ut JSON. Et felt som ikke finnes:

$ echo '{"name":"nethack"}' | jq '.nope'
null

null, og exit statusen er 0: et felt som mangler er et svar, ikke en feil. Nøsting er bare flere punktum:

$ echo '{"a":{"b":42}}' | jq '.a.b'
42

Det er hele ideen. Resten av guiden kjører mot én realistisk fil, så lagre tre poster fra en liten server som services.json: en spillserver, en nedlastingsjobb og ssh:

[
  { "name": "nethack", "state": "running", "port": 24565 },
  { "name": "curl", "state": "stopped", "port": null },
  { "name": "sshd", "state": "running", "port": 22 }
]

En array med tre objekter, hvert med et navn, en tilstand og en port. curl bærer null fordi den er stoppet, og noen her tar sitt nethack på alvor.

Utdatakontroll

To flagg endrer det som kommer ut. -r skriver ut rå strenger, uten JSON-anførselstegnene:

$ echo '{"name":"nethack"}' | jq -r '.name'
nethack

Når en verdi er på vei til en annen kommando, er -r det du vil ha. -c klemmer hver verdi inn på én linje:

$ echo '{"name":"nethack","state":"running"}' | jq -c .
{"name":"nethack","state":"running"}

Kompakt utdata setter postene tilbake i linjeland. Et filter kan også lese en fil direkte: gi filnavnet som siste argument, og ingen pipe trengs. Alle eksempler herfra leser services.json på denne måten.

$ jq -c '.[]' services.json
{"name":"nethack","state":"running","port":24565}
{"name":"curl","state":"stopped","port":null}
{"name":"sshd","state":"running","port":22}

Gå gjennom arrays og objekter

Eksempelfila services.json er en array, og arrays indekseres fra null:

$ echo '["alpha","beta","gamma"]' | jq '.[0]'
"alpha"
$ echo '["alpha","beta","gamma"]' | jq '.[]'
"alpha"
"beta"
"gamma"

.[0] plukker ett element. .[] er iteratoren: den produserer hvert element etter tur, og det er derfor svarene kom på hver sin linje. Før den inn i et felt, og du får det feltet fra hver post. Piped tilhører jq her. Inne i anførselstegnene gir bash den videre urørt:

$ jq '.[] | .name' services.json
"nethack"
"curl"
"sshd"

length teller elementer i en array og tegn i en streng:

$ echo '[10,20,30]' | jq 'length'
3
$ echo '"hello"' | jq 'length'
5

keys lister et objekts nøkler, sortert:

$ echo '{"zebra":1,"apple":2,"mango":3}' | jq 'keys'
[
  "apple",
  "mango",
  "zebra"
]

has() rapporterer om en nøkkel finnes, med en JSON-boolsk verdi:

$ echo '{"name":"nethack"}' | jq 'has("name")'
true
$ echo '{"name":"nethack"}' | jq 'has("nope")'
false

Velge og vrake

select() beholder verdiene som består en test, og treffene skrives ut én etter én:

$ jq '.[] | select(.state == "running")' services.json
{
  "name": "nethack",
  "state": "running",
  "port": 24565
}
{
  "name": "sshd",
  "state": "running",
  "port": 22
}

jqs //-fallback utløses ved et manglende felt, en null og en false-verdi. False biter: feltet .active under finnes helt ekte, og likevel setter // inn "on".

$ echo '{"name":"nethack"}' | jq '.nickname // "none"'
"none"
$ echo '{"name":"nethack","nickname":null}' | jq '.nickname // "none"'
"none"
$ echo '{"nickname":"bob"}' | jq '.nickname // "none"'
"bob"
$ echo '{"active":false}' | jq '.active // "on"'
"on"

-e gjør utdata om til en exit status. Er siste utdata false eller null, avslutter jq med 1, ellers 0:

$ echo '{"active":false}' | jq -e '.active'
false
$ echo $?
1

true avslutter med 0. Exit status 1 betyr at siste utdata var false eller null, og null-inndata lander der også, fordi null inn gir null ut. Exit status 4 betyr at filteret ikke produserte noe utdata i det hele tatt, som en select uten treff. Det er et annet tilfelle for et skript som forgrener seg på -e:

$ echo '{"a":1}' | jq -e '.a | select(. > 5)'
$ echo $?
4

Neste kjøring bruker --arg n nethack, som gir jq en variabel med navnet n. Sammenligningen skriver ut false, og jq avslutter likevel med 0. Uten -e rapporterer jq og lykkes uansett, så skript som forgrener seg på svaret trenger -e.

$ echo '{"name":"curl"}' | jq --arg n nethack '.name == $n'
false
$ echo $?
0

Når selve JSON-en er ødelagt

$ echo '{"name":"nethack"' | jq .
jq: parse error: Unfinished JSON term at EOF at line 2, column 0
$ echo $?
5

Parsefeil gir exit status 5, ikke 2, med identisk ordlyd på 1.8.1 og 1.8.2.

Omforming

Å hente ut felt er halve jobben. Krøllparenteser inne i et filter bygger ny JSON av bitene:

$ echo '{"name":"nethack","state":"running"}' | jq '{user: .name}'
{
  "user": "nethack"
}

Nøkkelen user er ny, verdien kommer fra .name, og det andre feltet blir igjen. map() kjører et filter på hvert element i en array og samler resultatene:

$ echo '[{"name":"nethack"},{"name":"curl"}]' | jq 'map(.name)'
[
  "nethack",
  "curl"
]

Sist kommer -s, slurp: noe inndata kommer som en strøm av JSON-verdier i stedet for som ett dokument, og -s leser hele strømmen inn i én array først:

$ printf '{"n":1}\n{"n":2}\n{"n":3}\n' | jq -s 'length'
3

Én ærlig begrensning: ekte jq-kode lenker disse konstruktene sammen til lengre pipelines, der map(select(...)) mater videre til nye pipes. Denne guiden holder seg bevisst til én idé per filter. Komposisjon er der jq blir både kraftfull og uleselig, og det får ikke plass i det som gjenstår.

JSON fra din egen maskin

To kommandoer på en vanlig Linux-boks sender ut JSON, i de to formene denne guiden har holdt atskilt. Den første er ip fra kapittelet Nettverk, som med -j skriver ut grensesnittene sine som én JSON-array:

$ ip -j addr | jq -r '.[].ifname'
lo
eth0

Én array, ett objekt per grensesnitt, lo først her, og .[0] gir deg det første objektet. Feltnavn som ifname tilhører iproute2. Grensesnittene er ulike, så ikke forvent de samme feltene overalt.

Den andre er journalctl fra kapittelet Tjenester og logger. -o json skriver ut oppføringene som JSON Lines: journalctl -n 5 -o json gir fem objekter på fem linjer, uten array rundt. Rekker du etter .[0], klager jq én gang per objekt:

jq: error (at <stdin>:1): Cannot index object with number

Fem slike linjer, én per post, og exit code 5. Meldingen er nøyaktig: objekter nekter numerisk indeksering. Archs jq 1.8.2 legger til et (0)-suffiks på hver linje. Fiksen er -s, som slurper strømmen med ett objekt per linje inn i én array først:

$ journalctl -n 5 -o json | jq -s 'length'
5

To forbehold. jq 'keys' uten -s skriver ut én keys-array per post, ikke én for alt. Og .MESSAGE er en ren streng, så journalctl -n 3 -o json | jq -r '.MESSAGE' skriver ut hver oppførings meldingstekst, én per linje, med anførselstegnene fjernet.

--arg er broen fra skallverdier og inn i filtre. Skallet utvider "$name" før jq i det hele tatt kjører:

$ name=nethack
$ echo '{"name":"nethack"}' | jq --arg n "$name" '.name == $n'
true

Peker du den mot {"name":"curl"} i stedet, er svaret false, igjen med exit status 0. --arg n "$name" definerer $n inne i filteret, fri for skallets utvidelse.

Så fellen denne guiden skylder deg. Enkle filtre overlever uten anførselstegn, fordi et bart .name ikke rommer noe bash vil ha:

$ echo '{"name":"nethack"}' | jq .name
"nethack"

Det virket, exit status 0 og alt sammen. Legg til et tegn med sidejobb for skallet, og versjonen uten anførselstegn faller fra hverandre på tre måter. Piped deler kommandoen, og bash prøver å kjøre et program med navnet .name:

$ echo '{"name":"nethack"}' | jq .[] | .name
bash: .name: command not found

Exit 127, og den feilen er bashs, ikke jqs. En // uten anførselstegn ender med jq som rapporterer jq: error: Could not open file none: No such file or directory og exit status 2. Krøllparenteser uten anførselstegn gir jq halve filteret, som returnerer en kompileringsfeil og exit status 3. Sett enkelanførselstegn rundt filteret, alltid: det er den eneste formen som aldri overrasker deg.

Avslutningsprosjekt — telle det som kjører

Alt ovenfor folder seg sammen til et lite skript som tar imot en JSON-fil, beholder postene som kjører, og teller dem:

#!/bin/bash
# running-count.sh -- count records in the running state
count=$(jq -c '.[] | select(.state == "running")' "$1" | wc -l)
echo "$1: $count running"

Her jobber to pipes: jqs egen, inne i anførselstegnene, mellom iteratoren og select, og en pipe i skallet som sender jqs utdata videre til wc -l fra kapittelet Tekstbehandling. -c legger hvert treff på sin egen linje, og det er det som gjør wc -l til en posttelling:

$ bash running-count.sh services.json
services.json: 2 running

Hvor du står nå

Du kan lese begge JSON-formene en Linux-maskin sender ut, og hente felt og poster fra begge. Vanene: enkelanførselstegn rundt filtrene, -r for rå tekst, -s for inndata med ett objekt per linje, og -e når et skript trenger dommen som exit status. Dypere jq venter utenfor denne guiden: reduce, def, streaming, jqplay, generering av JSON og live-kall mot eksterne API-er er alle utenfor omfanget her, og hvert eksempel over kjørte lokalt, så ingenting av det råtner.

Til skallsiden av dagens kommandoer har kapittelet Shell-grunnleggende pipene. Når det gjelder anførselstegn, se fellen ovenfor. To referanser dekker resten:

  • jq-manualen — hele filterspråket og hvert flagg som er brukt her
  • jq på GitHub — utgivelser, issue tracker og kilden