JQ: выберите, когда любое значение находится в массиве
Учитывая входные данные JSON
[
{"title": "first line"},
{"title": "second line"},
{"title": "third line"}
]
Как мы можем извлечь только заголовки, которые содержат ключевые слова, которые перечислены во втором массиве "фильтр". Используя переменную оболочки здесь, например:
filter='["second", "third"]'
Выход в этом случае будет
[
{"title": "second line"},
{"title": "third line"}
]
Кроме того, как использовать фильтр массива вместо этого. Например: вернуть только запись "первая строка" в предыдущем примере.
Есть похожий ответ, но с использованием старой версии jq. Я надеюсь, что есть более интуитивный / читабельный способ сделать это с текущей версией jq.
3 ответа
Вы можете использовать комбинацию jq
и трюки с использованием массивов для создания фильтра. Во-первых, чтобы создать массив оболочки, используйте обозначение массива из оболочки, как показано ниже. Обратите внимание, что ниже обозначение bash
массивы не возьмут ,
в качестве разделителя в его определении. Теперь нам нужно создать фильтр регулярных выражений для соответствия строке, поэтому мы создаем оператор чередования
filter=("first" "second")
echo "$(IFS="|"; echo "${filter[*]}"
first|second
Вы не упомянули, совпадает ли строка только в первом или последнем или может быть где-нибудь в .title
раздел. Приведенное ниже регулярное выражение соответствует строке в любом месте строки.
Теперь мы хотим использовать этот фильтр в jq
сопоставить с .title
Строка, как показано ниже. Обратите внимание на использование not
отрицать результат. Чтобы обеспечить фактическое совпадение, удалите деталь |not
,
jq --arg re "$(IFS="|"; echo "${filter[*]}")" '[.[] | select(.title|test($re)|not)]' < json
Одним из способов решения проблемы, которая включает слово "любой", часто является использование jq any
Например, используя вашу переменную оболочки:
jq --argjson filter "$filter" '
map((.title | split(" ")) as $title
| select(any( $title[] as $t
| $filter[] as $kw
| $kw == $t )))' input.json
Отрицание
Как и в формальной логике, вы можете использовать all
или же any
(в сочетании с отрицанием), чтобы решить отрицательную проблему. Но не забывайте, что если вы используете not
JQ's not
фильтр нулевой арности
jq --argjson filter "$filter" '
map((.title | split(" ")) as $title
| select(all( $title[] as $t
| $filter[] as $kw
| $kw != $t )))' input.json
Другие подходы
Вышеприведенное слово использует "сопоставление по ключевым словам", поскольку именно это определяет вопрос, но, конечно, вышеприведенные выражения jq можно легко изменить, используя регулярные выражения или какой-либо другой тип сопоставления.
Если список ключевых слов очень длинный, то лучший алгоритм пересечения массивов, несомненно, был бы желателен.
Если вы не ограничены jq, могу ли я предложить вам альтернативное решение, основанное на другой утилите JSON - jtc
:
если бы вы могли изменить свой массив фильтрации из ["second", "third"]
в связанное регулярное выражение, как это:
bash $ filter='["second", "third"]'
bash $ re_filter=$(echo $filter | sed -E 's/"([^"]+)"/(\1)/g; s/[][]//g; s/, /\|/g')
bash $ echo $re_filter
(second)|(third)
bash $
тогда оба ваших запроса становятся тривиальными с jtc
:
1. распечатать элементы, находящиеся в фильтре:
bash $ cat input.json | jtc -w"<$re_filter>R:" -njl
[
{
"title": "second line"
},
{
"title": "third line"
}
]
bash $
2. распечатать все элементы, кроме тех, которые находятся в фильтре:
bash $ cat input.json | jtc -w"<$re_filter>R: [-1]" -p
[
{
"title": "first line"
}
]
bash $