How to select multiple items with duplicate items using jq? - json

I have a JSON file which contains a list like this;
[{
"host": "cat",
"ip": "192.168.1.1",
"id": "cherry"
}, {
"host": "dog",
"ip": "192.168.1.1",
"id": "apple"
}, {
"host": "cat",
"ip": "192.168.1.2",
"id": "banana"
}]
I want to collect IPs and print id and host next to it but if IP is used multiple times then print multiple id and host next to instead of a new line. IP and host can be the same for multiple items but id is unique.
So the final output should look like this;
$ echo <something>
192.168.1.1 cat cherry dog apple
192.168.1.2 cat banana
How can I do this using bash and jq?

Make sure you have a valid JSON file: Remove the last comma , in each object to get this as your input.json:
[{
"host": "cat",
"ip": "192.168.1.1",
"id": "cherry"
}, {
"host": "dog",
"ip": "192.168.1.1",
"id": "apple"
}, {
"host": "cat",
"ip": "192.168.1.2",
"id": "banana"
}]
Then, you only need one jq call:
jq --raw-output 'group_by(.ip)[] | [first.ip, (.[] | .host, .id)] | join(" ")' input.json
Demo

Once you fix your example so it's valid JSON, the group_by function is the key:
$ jq -r 'group_by(.ip)[] | [.[0].ip, map(.host, .id)[]] | #tsv' input.json
192.168.1.1 cat cherry dog apple
192.168.1.2 cat banana
That will combine all objects with the same ip field into an array of objects. The rest is just turning those array of objects into arrays of just the values you want, and finally outputting each new array as a line of tab-separated values.

Related

jq to extract specific data with different map name of same type

I'm performing a query on vault API to list all the entities by their Ids and trying extract alias name from aliases.name, however, due to the different map name in each iteration I'm not able to extract the value.
Command
curl -s --header "X-Vault-Token: $TOKEN" --request LIST http://localhost:8200/v1/identity/entity/id | jq ".data.key_info"
Result
{
"923104b3-910c-05b1-b448-fcg3d67276e1": {
"aliases": [
{
"id": "6a6f2dc4-2cd8-1881-662f-b5955e765e0d",
"mount_accessor": "auth_oidc_053b2418",
"mount_path": "auth/oidc/",
"mount_type": "oidc",
"name": "John Doe"
}
],
"name": "entity_402ea123"
},
"ba6b00c4-36gg-gd86-86bf-13d731588241": {
"aliases": [
{
"id": "3ji408b2-4548-75f7-8c41-9901b77af7d1",
"mount_accessor": "auth_oidc_053b2418",
"mount_path": "auth/oidc/",
"mount_type": "oidc",
"name": "Jane Smith"
}
],
"name": "entity_98746ae4"
}
}
Expected output
"name": "John Doe"
"name": "Jane Smith"
If you are trying to extract a stream of ojects with just the name field:
… | jq -c ".data.key_info[] | .aliases[] | {name}"
{"name":"John Doe"}
{"name":"Jane Smith"}
Demo
If you are trying to extract them as an array, use map:
… | jq ".data.key_info | map(.aliases[] | {name})"
[
{
"name": "John Doe"
},
{
"name": "Jane Smith"
}
]
Demo
If you just need the values, use the -r option:
… | jq -r ".data.key_info[].aliases[].name"
John Doe
Jane Smith
Demo

Format json output with jq

I'm writing a script which will obtain certain info from my Kubernetes cluster.
The following command
kubectl get --context <my-context> svc --selector='<my-selectors>' -o json |
jq -r ' .items[]| {Name:.metadata.name, Port:.spec.ports[0].port} + {Group: "test", Group: "group name", SSLMode: "prefer", MaintenanceDB: "postgres"} '>file.json
will output something like this
{
"Name": "db-name",
"Port": 3000,
"Group": "group name",
"SSLMode": "prefer",
"MaintenanceDB": "postgres"
}
{
"Name": "db-name",
"Port": 5432,
"Group": "group name",
"SSLMode": "prefer",
"MaintenanceDB": "postgres"
}
I've been trying to get the above into the following format
{
"Servers":{
"1": {
"Name": "db-name",
"Port": 3000,
"Group": "Server Group 1",
"SSLMode": "prefer",
"MaintenanceDB": "postgres"
},
"2": {
"Name": "db-name",
"Port": 5432,
"Group": "Server Group 1",
"SSLMode": "prefer",
"MaintenanceDB": "postgres"
}
}
}
Only just discovered jq so the few things I've tried have been unsuccessful. Would be grateful for any pointers.
Given the stream of JSON objects shown in the question, the following jq filter will produce the desired output assuming the stream is somehow "slurped":
. as $in
| reduce range(0;length) as $i ({};.[$i+1|tostring] = $in[$i])
| {Servers: .}
To avoid having to call jq twice, you could wrap your filter in square brackets, and pipe that into the above; better yet, you can streamline everything, e.g. along the following lines:
.items
| [to_entries[]
| {(.key+1|tostring): .value}
| map_values(
{Name:.metadata.name,
Port:.spec.ports[0].port,
Group: "group name",
SSLMode: "prefer",
MaintenanceDB: "postgres"}) ]
| {Servers: add}

Print key and value for different entries in an object

I need to print some results with jq to take json.
This is an example:
{
"data": [
{
"time": 20201606,
"event": {
"ip": "127.0.1",
"hostname": "srv1",
"locations": [
"UK",
"site1"
],
"num": 1
}
},
{
"time": 202016034,
"event": {
"ip": "127.0.2",
"hostname": "srv2",
"locations": [
"UK",
"site2"
],
"num": 3
}
}
]
}
Like to generate this output "num, ip, hostname, locations":
1, srv1, 127.0.1, UK,site1
2, srv2, 127.0.2, HK,site2
3, srv3, 127.0.3, LO,site3
How can I print this via jq?
Join locations by a comma, and put the result into an array with other fields. Then join again by a comma followed by a space to get the desired output format. E.g.:
.data[].event | [
.num,
.hostname,
.ip,
(.locations | join(",")) ?
] | join(", ")
Use --raw-output/-r option in the command line invocation to get raw strings instead of JSON strings.
Online demo
At its core, you want to build an array consisting of the values you want:
$ jq '.data[].event | [.num, .hostame, .ip, .locations]' tmp.json
[
1,
null,
"127.0.1",
[
"UK",
"site1"
]
]
[
3,
null,
"127.0.2",
[
"UK",
"site2"
]
]
From there, it's a matter of formatting. First, let's turn the list of locations into a single string:
$ jq '.data[].event | [.num, .hostame, .ip, (.locations|join(","))]' tmp.json
[
1,
null,
"127.0.1",
"UK,site1"
]
[
3,
null,
"127.0.2",
"UK,site2"
]
Next, let's join those strings into a ", "-separated string.
$ jq '.data[].event | [.num, .hostame, .ip, (.locations|join(","))] | join(", ")' tmp.json
"1, , 127.0.1, UK,site1"
"3, , 127.0.2, UK,site2"
Finally, you can use the -r flag to output raw text rather than a JSON string value.
$ jq -r '.data[].event | [.num, .hostame, .ip, (.locations|join(","))] | join(", ")' tmp.json
1, , 127.0.1, UK,site1
3, , 127.0.2, UK,site2

How to output keys on different levels if value found in array

Using jq, I would like to output multiple values on different levels of a JSON file based on whether they exist in an array.
My data looks like the following. It displays a number of hosts I examine regarding the people who have access to it:
[
{
"server": "example_1",
"version": "Debian8",
"keys": [
{
"fingerprint": "SHA256:fingerprint1",
"for_user": "root",
"name": "user1"
},
{
"fingerprint": "SHA256:fingerprint2",
"for_user": "git",
"name": "user2"
}
]
},
{
"server": "example_2",
"version": "Debian9",
"keys": [
{
"fingerprint": "SHA256:fingerprint2",
"for_user": "root",
"name": "user2"
},
{
"fingerprint": "SHA256:fingerprint2",
"for_user": "www",
"name": "user2"
}
]
},
{
"server": "example_3",
"version": "CentOS",
"keys": [
null
]
}
]
I want to extract the value for server and the value of for_user any occurence where user2 is found as a name in .keys[]. Basically, the output could look like this:
example1, git
example2, root
example2, www
What I can already do is displaying the first column, so the .server value:
cat test.json | jq -r '.[] | select(.keys[].name | index("user2")) | .server'`
How could I also print a value in the selected array element?
You can use the following jq command:
jq -r '.[]|"\(.server), \(.keys[]|select(.name=="user2").for_user)"'

Lookup filtering with jq

Giving a JSON string like this,
[
{
"id": 1,
"name": "Arthur",
"age": "21"
},
{
"id": 2,
"name": "Richard",
"age": "32"
}
]
How to filter by name and get the age?
E.g., given the name being "Richard", let jq return "32". Thx.
$ jq --arg name Richard '.[] | select(.name==$name) | .age' input.json
"32"
When using jq like this in Windows, the quoting would have to be appropriate for Windows.