Jq — извлечь значения родительского ключа в виде значений, разделенных запятыми, рядом с дочерними элементами

Я пытаюсь извлечь диапазоны из приведенного ниже вывода JSON,

{
    "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"
                }
            ]
        }
    }
}

Который я успешно могу извлечь с помощью следующей команды:

.["zscaler.net"]["continent : EMEA"] | .[][].range

Я ищу, чтобы попытаться получить родительский континент и родительский город каждого диапазона рядом друг с другом, как через запятую,

165.225.240.0/23, EMEA, Amsterdam II

147.161.132.0/23, EMEA, Amsterdam II

147.161.156.0/23, EMEA, Brussels II

Я пытаюсь добиться этого полностью в jq и bash.

Пожалуйста помоги.

Вам это нужно для "continent : EMEA" или для всех городов?

Inian 11.04.2023 10:40

Требование было отредактировано, извините за это. Но я хочу получить родительские значения континента и города рядом с каждым из диапазонов.

deadshotpro 11.04.2023 10:46

Не уверен, что этот комментарий ответил на мой вопрос. Вам нужен вышеуказанный вывод для американского региона или нет?

Inian 11.04.2023 10:54

Да, это будет для всех регионов

deadshotpro 11.04.2023 10:57
Как сделать HTTP-запрос в Javascript?
Как сделать HTTP-запрос в Javascript?
В JavaScript вы можете сделать HTTP-запрос, используя объект XMLHttpRequest или более новый API fetch. Вот пример для обоих методов:
2
4
131
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Вы можете использовать to_entries в сочетании с sub() для очистки ключа, затем использовать join(",") (или @csv), чтобы получить желаемый результат:

.["zscaler.net"]["continent : EMEA"] 
    | to_entries[] 
    | .key as $k 
    | .value[] 
    | [ .range, ($k | sub("city : "; "" )) ] | join(", ")

  1. Сначала мы нацеливаемся на правильный объект
    .["zscaler.net"]["continent : EMEA"]
    
  2. Затем мы используем to_entries, чтобы получить ключ объекта.
  3. Сохраните ключ: .key as $k
  4. За каждый ([]) предмет в .value
  5. Создайте новый массив, содержащий
    • .range текущего объекта
    • Ключ ($k), но мы используем sub, чтобы заменить city : ничем ("")
  6. join(", ") вернуть массив в строку csv

"165.225.240.0/23, Amsterdam II"
"147.161.132.0/23, Amsterdam II"
"147.161.156.0/23, Brussels II"

Демонстрация JqPlay


Если вам нужен вывод для каждого континента, удалите жестко запрограммированный ["continent : EMEA"], его можно заменить другим [], чтобы перебрать их и получить следующий вывод:

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

Демо

Я считаю, что это то же самое, что и мой вывод? Пожалуйста, добавьте желаемый результат к вашему вопросу

0stone0 11.04.2023 11:01

165.225.240.0/23, EMEA, Амстердам II

deadshotpro 11.04.2023 11:09

Кроме того, мне бы очень хотелось краткое объяснение вашей команды. Я все еще учусь jq, изо всех сил стараюсь подняться туда.

deadshotpro 11.04.2023 11:12

Я добавил более подробное объяснение

0stone0 11.04.2023 11:29

Кроме того, FYI - OP хочет, чтобы это было для всех регионов - stackoverflow.com/questions/75983859/…

Inian 11.04.2023 11:32

Добавлены пояснения по замене ["continent : EMEA"] другим [] для перебора нескольких регионов.

0stone0 11.04.2023 11:36

Что-то не так с выводом/объяснением @deadshotpro?

0stone0 11.04.2023 13:24

@0stone0 Ваш ответ действительно помог. Это был не тот результат, который я искал. Тем не менее, мне удалось состряпать что-то свое, прослушав вашу команду и объяснение. Огромное спасибо за это!

deadshotpro 11.04.2023 15:23

Вы можете комбинировать нужные поля вот так \(.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

Спасибо, это полезно и информативно. Прямо сейчас в требование внесено редактирование, извините за это. Но я хочу получить родительские значения континента и города рядом с каждым из диапазонов.

deadshotpro 11.04.2023 10:49

Вот решение, которое соответствует желаемому результату вопроса:

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

Вот еще один способ сделать это с помощью 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"]
[["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"]]

Вы можете видеть в приведенном выше результате, что каждая строка, где путь к элементу потока (первый элемент массива, который является массивом), который заканчивается на "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"]
[["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"]

Чтобы использовать 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"]
["147.161.156.0/23"," EMEA"," Brussels II"]
["104.129.204.0/23"," Americas"," Atlanta II"]
["136.226.2.0/23"," Americas"," Atlanta II"]

Осталось только отформатировать вывод в формате CSV с помощью join(","), как показано в исходном решении выше.

Вау! Это совсем сокращение. Но это работает как шарм. Не могли бы вы объяснить, что вы сделали?

deadshotpro 11.04.2023 13:42

@deadshotpro Я расширил свой ответ дополнительными пояснениями.

rickhg12hs 12.04.2023 09:29

Другие вопросы по теме