提取父键的值,作为逗号分隔的值,位于子元素旁边。

huangapple go评论59阅读模式
英文:

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_entriessub() 来清理键,然后使用 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(", ")

  1. First we target the correct object
    .["zscaler.net"]["continent : EMEA"]
    
  2. Then we use to_entries to get the object key
  3. Save the key: .key as $k
  4. For each ([]) item in .value
  5. Create a new array, containing
    • The .range of the current object
    • The key ($k), but we use sub to replace city : with nothing ("")
  6. 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]访问了上面的结果流的每个元素的第一个数组的最后一个数组元素。其余部分只是一些过滤和格式化。

将管道数据限制为只有"range"

.["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

huangapple
  • 本文由 发表于 2023年4月11日 16:28:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/75983859.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定