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 (в сочетании с отрицанием), чтобы решить отрицательную проблему. Но не забывайте, что если вы используете notJQ'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 $ 
Другие вопросы по тегам