英文:
jq - extract parent key values as comma separated values next to child elements
问题
我正在尝试从下面的JSON输出中提取范围,
.["zscaler.net"]["continent : EMEA"] | .[][].range
我正在尝试在jq和bash中完成这个任务,你能帮忙吗?
英文:
I am trying to extract the ranges from the below JSON output,
{
"zscaler.net": {
"continent : EMEA": {
"city : Amsterdam II": [
{
"range": "165.225.240.0/23",
"vpn": "ams2-2-vpn.zscaler.net",
"gre": "165.225.240.12",
"hostname": "ams2-2.sme.zscaler.net",
"latitude": "52",
"longitude": "5"
},
{
"range": "147.161.132.0/23",
"vpn": "",
"gre": "",
"hostname": "",
"latitude": "52",
"longitude": "5"
}
],
"city : Brussels II": [
{
"range": "147.161.156.0/23",
"vpn": "",
"gre": "",
"hostname": "",
"latitude": "50",
"longitude": "5"
}
]
},
"continent : Americas": {
"city : Atlanta II": [
{
"range": "104.129.204.0/23",
"vpn": "atl2-vpn.zscaler.net",
"gre": "104.129.204.32",
"hostname": "atl2.sme.zscaler.net",
"latitude": "34",
"longitude": "-84"
},
{
"range": "136.226.2.0/23",
"vpn": "",
"gre": "104.129.204.32",
"hostname": "",
"latitude": "34",
"longitude": "-84"
}
]
}
}
}
Which I am successfully able to extract using the following command,
.["zscaler.net"]["continent : EMEA"] | .[][].range
What I am looking for is to try to get the parent continent and parent city of each range beside each other as comma separated,
165.225.240.0/23, EMEA, Amsterdam II
147.161.132.0/23, EMEA, Amsterdam II
147.161.156.0/23, EMEA, Brussels II
I am trying to achieve this completely in jq and bash.
Please help.
答案1
得分: 3
你可以使用 to_entries
与 sub()
来清理键,然后使用 join(",")
(或 @csv
)来获得所需的输出:
"165.225.240.0/23, Amsterdam II"
"147.161.132.0/23, Amsterdam II"
"147.161.156.0/23, Brussels II"
如果要针对每个大陆输出结果,可以删除硬编码的 ["continent : EMEA"]
,可以将其替换为另一个 []
以循环遍历它们并获得以下输出:
165.225.240.0/23, Amsterdam II
147.161.132.0/23, Amsterdam II
147.161.156.0/23, Brussels II
104.129.204.0/23, Atlanta II
136.226.2.0/23, Atlanta II
英文:
You can use to_entries
combined with sub()
to clean the key, then use join(",")
(or @csv
) to get the desired output:
.["zscaler.net"]["continent : EMEA"]
| to_entries[]
| .key as $k
| .value[]
| [ .range, ($k | sub("city : "; "" )) ] | join(", ")
- First we target the correct object
.["zscaler.net"]["continent : EMEA"]
- Then we use
to_entries
to get the object key - Save the key:
.key as $k
- For each (
[]
) item in.value
- Create a new array, containing
- The
.range
of the current object - The key (
$k
), but we usesub
to replacecity :
with nothing (""
)
- The
join(", ")
back the array to a csv string
"165.225.240.0/23, Amsterdam II"
"147.161.132.0/23, Amsterdam II"
"147.161.156.0/23, Brussels II"
JqPlay Demo
If you'd like the output for each continent, so remove the hardcoded ["continent : EMEA"]
, it can be replaced with another []
to loop over them and get the following output:
.["zscaler.net"][] | to_entries[] | .key as $k | .value[] | [ .range, ($k | sub("city : "; "" )) ] | join(", ")
165.225.240.0/23, Amsterdam II
147.161.132.0/23, Amsterdam II
147.161.156.0/23, Brussels II
104.129.204.0/23, Atlanta II
136.226.2.0/23, Atlanta II
Demo
答案2
得分: 3
以下是您要的翻译内容:
.["zscaler.net"] | to_entries[]
| (.key|ltrimstr("continent : ")) as $cont
| .value | to_entries[]
| "\(.value[].range), \($cont), \(.key|ltrimstr("city : "))"
输出:
165.225.240.0/23, EMEA, Amsterdam II
147.161.132.0/23, EMEA, Amsterdam II
147.161.156.0/23, EMEA, Brussels II
104.129.204.0/23, Americas, Atlanta II
136.226.2.0/23, Americas, Atlanta II
英文:
Here's the solution that matches the desired output from the question:
.["zscaler.net"] | to_entries[]
| (.key|ltrimstr("continent : ")) as $cont
| .value | to_entries[]
| "\(.value[].range), \($cont), \(.key|ltrimstr("city : "))"
Output:
165.225.240.0/23, EMEA, Amsterdam II
147.161.132.0/23, EMEA, Amsterdam II
147.161.156.0/23, EMEA, Brussels II
104.129.204.0/23, Americas, Atlanta II
136.226.2.0/23, Americas, Atlanta II
答案3
得分: 2
这是使用tostream
另一种方法。
tostream
的输出将包含每个值的所有父项和键。此流可以使用select
来筛选range
键,然后提取/格式化/输出流元素的所需部分。
.["zscaler.net"] | tostream
| select(.[0][-1] == "range")
| [.[-1], .[0][0:2][]|split(":")[-1]]
| join(",")
示例输出:
165.225.240.0/23, EMEA, Amsterdam II
147.161.132.0/23, EMEA, Amsterdam II
147.161.156.0/23, EMEA, Brussels II
104.129.204.0/23, Americas, Atlanta II
136.226.2.0/23, Americas, Atlanta II
在jqplay.org上尝试它。
解决方案详解
这个解决方案的灵感来自于认识到所需的所有输出字段都包含在流输出的单个元素中。了解管道可能最容易通过查看每个阶段后发生的情况。
筛选:
.["zscaler.net"] | tostream
结果:
[["continent : EMEA","city : Amsterdam II",0,"range"],"165.225.240.0/23"]
[["continent : EMEA","city : Amsterdam II",0,"vpn"],"ams2-2-vpn.zscaler.net"]
[["continent : EMEA","city : Amsterdam II",0,"gre"],"165.225.240.12"]
...
您可以在上面的结果中看到,流元素的路径(数组的第一个元素,它本身是一个数组)以"range"
结束的每一行都包含所需输出的所有字段。.[0][-1]
访问了上面的结果流的每个元素的第一个数组的最后一个数组元素。其余部分只是一些过滤和格式化。
.["zscaler.net"] | tostream
| select(.[0][-1] == "range")
结果:
[["continent : EMEA","city : Amsterdam II",0,"range"],"165.225.240.0/23"]
[["continent : EMEA","city : Amsterdam II",1,"range"],"147.161.132.0/23"]
...
要使用join(",")
在最后创建所需的CSV,需要“提取”数据并将其放入数组中。
从上面的结果中,"range"
的值通过.[-1]
访问,即数组的最后一个元素。"continent"
和"city"
是数组的第一个和第二个元素,本身是整体数组的第一个元素,即.[0][0:2]
。只需从这些元素中提取值,这可以通过split(":")
完成。由于这需要对每个元素进行操作,可以使用.[0][0:2][]
来迭代每个元素。将其传输到split(":")
以按所需的格式输出。
过滤:
.["zscaler.net"] | tostream
| select(.[0][-1] == "range")
| [.[-1], .[0][0:2][]|split(":")[-1]]
结果:
["165.225.240.0/23"," EMEA"," Amsterdam II"]
["147.161.132.0/23"," EMEA"," Amsterdam II"]
...
现在只需使用join(",")
将输出格式化为CSV,如上面的原始解决方案所示。
英文:
Here's another way to do it by using tostream
.
The output of tostream
will have all the parents and key for each value. This stream can be filtered (select
) for the range
key and then the desired parts of the stream element can be extracted/formatted/output.
.["zscaler.net"] | tostream
| select(.[0][-1] == "range")
| [.[-1], .[0][0:2][]|split(":")[-1]]
| join(",")
Example output:
165.225.240.0/23, EMEA, Amsterdam II
147.161.132.0/23, EMEA, Amsterdam II
147.161.156.0/23, EMEA, Brussels II
104.129.204.0/23, Americas, Atlanta II
136.226.2.0/23, Americas, Atlanta II
Try it on [jqplay.org](https://jqplay.org/s/d0xhGQMiRJf "Click me!").
Solution Elaboration
The inspiration behind this solution was recognizing that all the desired output fields are contained in a single element of the stream output. Understanding the pipeline might be easiest by seeing what happens after each stage.
Filter:
.["zscaler.net"] | tostream
[Result](https://jqplay.org/s/PouQf4LDuE1 "link to jqplay -->"):
[["continent : EMEA","city : Amsterdam II",0,"range"],"165.225.240.0/23"]
[["continent : EMEA","city : Amsterdam II",0,"vpn"],"ams2-2-vpn.zscaler.net"]
[["continent : EMEA","city : Amsterdam II",0,"gre"],"165.225.240.12"]
[["continent : EMEA","city : Amsterdam II",0,"hostname"],"ams2-2.sme.zscaler.net"]
[["continent : EMEA","city : Amsterdam II",0,"latitude"],"52"]
[["continent : EMEA","city : Amsterdam II",0,"longitude"],"5"]
[["continent : EMEA","city : Amsterdam II",0,"longitude"]]
[["continent : EMEA","city : Amsterdam II",1,"range"],"147.161.132.0/23"]
[["continent : EMEA","city : Amsterdam II",1,"vpn"],""]
[["continent : EMEA","city : Amsterdam II",1,"gre"],""]
[["continent : EMEA","city : Amsterdam II",1,"hostname"],""]
[["continent : EMEA","city : Amsterdam II",1,"latitude"],"52"]
[["continent : EMEA","city : Amsterdam II",1,"longitude"],"5"]
[["continent : EMEA","city : Amsterdam II",1,"longitude"]]
[["continent : EMEA","city : Amsterdam II",1]]
[["continent : EMEA","city : Brussels II",0,"range"],"147.161.156.0/23"]
[["continent : EMEA","city : Brussels II",0,"vpn"],""]
[["continent : EMEA","city : Brussels II",0,"gre"],""]
[["continent : EMEA","city : Brussels II",0,"hostname"],""]
[["continent : EMEA","city : Brussels II",0,"latitude"],"50"]
[["continent : EMEA","city : Brussels II",0,"longitude"],"5"]
[["continent : EMEA","city : Brussels II",0,"longitude"]]
[["continent : EMEA","city : Brussels II",0]]
[["continent : EMEA","city : Brussels II"]]
[["continent : Americas","city : Atlanta II",0,"range"],"104.129.204.0/23"]
[["continent : Americas","city : Atlanta II",0,"vpn"],"atl2-vpn.zscaler.net"]
[["continent : Americas","city : Atlanta II",0,"gre"],"104.129.204.32"]
[["continent : Americas","city : Atlanta II",0,"hostname"],"atl2.sme.zscaler.net"]
[["continent : Americas","city : Atlanta II",0,"latitude"],"34"]
[["continent : Americas","city : Atlanta II",0,"longitude"],"-84"]
[["continent : Americas","city : Atlanta II",0,"longitude"]]
[["continent : Americas","city : Atlanta II",1,"range"],"136.226.2.0/23"]
[["continent : Americas","city : Atlanta II",1,"vpn"],""]
[["continent : Americas","city : Atlanta II",1,"gre"],"104.129.204.32"]
[["continent : Americas","city : Atlanta II",1,"hostname"],""]
[["continent : Americas","city : Atlanta II",1,"latitude"],"34"]
[["continent : Americas","city : Atlanta II",1,"longitude"],"-84"]
[["continent : Americas","city : Atlanta II",1,"longitude"]]
[["continent : Americas","city : Atlanta II",1]]
[["continent : Americas","city : Atlanta II"]]
[["continent : Americas"]]
You can see in the result above that each line where the stream element's path (the first element of the array, which is an array) that ends with "range"
has all the fields in the desired output. .[0][-1]
accesses the the last array element of the first array for each element of the result stream above. The rest is just some filtering and formatting.
[Limit the pipeline data to just the "range"
lines](https://jqplay.org/s/LgguLhoXTA1 "link to jqplay -->"):
.["zscaler.net"] | tostream
| select(.[0][-1] == "range")
Result:
[["continent : EMEA","city : Amsterdam II",0,"range"],"165.225.240.0/23"]
[["continent : EMEA","city : Amsterdam II",1,"range"],"147.161.132.0/23"]
[["continent : EMEA","city : Brussels II",0,"range"],"147.161.156.0/23"]
[["continent : Americas","city : Atlanta II",0,"range"],"104.129.204.0/23"]
[["continent : Americas","city : Atlanta II",1,"range"],"136.226.2.0/23"]
To use join(",")
at the end to create the desired CSV, the data needs to be "extracted" and put in an array.
From the above result, the "range"
value is accessed with .[-1]
, i.e., the last element of the array. The "continent"
and the "city"
are the first and second element of the array that is itself the first element of the overall array, i.e., .[0][0:2]
. Just the value needs to be extracted from these elements and this can be done with split(":")
. Since this needs to be done for each element, you can iterate each element with .[0][0:2][]
. Piping this into split(":")
formats the output as desired.
[Filter](https://jqplay.org/s/Q_VRLVyIXJQ "link to jqplay -->"):
.["zscaler.net"] | tostream
| select(.[0][-1] == "range")
| [.[-1], .[0][0:2][]|split(":")[-1]]
Result:
["165.225.240.0/23"," EMEA"," Amsterdam II"]
["147.161.132.0/23"," EMEA"," Amsterdam II"]
["147.161.156.0/23"," EMEA"," Brussels II"]
["104.129.204.0/23"," Americas"," Atlanta II"]
["136.226.2.0/23"," Americas"," Atlanta II"]
All that's left is to format the output as CSV with join(",")
as shown in the original solution above.
答案4
得分: 1
你可以像这样组合所需的字段 \(.first) \(.second)
:
jq -r '.["zscaler.net"]["continent : EMEA"] | .[][]|"\(.range), \(.latitude), \(.longitude)"'
输出:
165.225.240.0/23, 52, 5
147.161.132.0/23, 52, 5
147.161.156.0/23, 50, 5
英文:
You can combine the needed fields like so \(.first) \(.second)
:
jq -r '.["zscaler.net"]["continent : EMEA"] | .[][]|"\(.range), \(.latitude), \(.longitude)"'
Output:
165.225.240.0/23, 52, 5
147.161.132.0/23, 52, 5
147.161.156.0/23, 50, 5
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论