как использовать jq для фильтрации выбранных элементов, которых нет в списке?

В jq я могу довольно легко выбрать элемент в списке:

$ echo '["a","b","c","d","e"]' | jq '.[] | select(. == ("a","c"))'

Или, если вы предпочитаете получить его как массив:

$ echo '["a","b","c","d","e"]' | jq 'map(select(. == ("a","c")))'

Но как мне выбрать все элементы, которых нет в списке? Конечно . != ("a","c") не работает:

$ echo '["a","b","c","d","e"]' | jq 'map(select(. != ("a","c")))'
[
  "a",
  "b",
  "b",
  "c",
  "d",
  "d",
  "e",
  "e"
]

Вышеупомянутый каждый элемент дает дважды, за исключением "a" и "c "

То же самое для:

$ echo '["a","b","c","d","e"]' | jq '.[] | select(. != ("a","c"))'
"a"
"b"
"b"
"c"
"d"
"d"
"e"
"e"

Как отфильтровать исключить совпадающие элементы?


person deitch    schedule 15.06.2017    source источник
comment
Это было жестоко больно, но мне удалось это понять.   -  person deitch    schedule 15.06.2017
comment
Ваш фильтр фактически такой же, как . != "a" or . != "c". Это, конечно, всегда будет правдой, поэтому вы не видите ничего отфильтрованного. Однако теперь вы получаете дубликаты, поскольку используете оператор запятой. Помните, что для каждого значения, полученного из запятых, выражение повторно вычисляется с новыми значениями. Итак, select(. != ("a","c")) становится select(. != "a"), select(. != "c"). Тогда должно быть очень ясно, что происходит.   -  person Jeff Mercado    schedule 16.06.2017
comment
Спасибо за объяснение @JeffMercado. Я не мог понять, почему это не сработало. По сути, . != ("a","c") - это логическое ИЛИ, где я ожидал логического И (хотя . == ("a","c") является логическим ИЛИ).   -  person deitch    schedule 16.06.2017
comment
Не совсем. Это больше похоже на то, что ("a","c") - это два значения "a" и "c". Для любого выражения, которое его использует, скопируйте выражение, подставив значения "a" и "c" для копий.   -  person Jeff Mercado    schedule 16.06.2017


Ответы (2)


Самым простым и надежным (для версий jq) подходом было бы использование встроенного -:

$ echo '["a","b","c","d","e"]' | jq -c '. - ["a","c"]'
["b","d","e"]

Если черный список очень длинный и изобилует дубликатами, то, возможно, целесообразно удалить их (например, с помощью unique).

Вариации

Проблема также может быть решена (в jq 1.4 и выше) с помощью index и not, например.

["a","c"] as $blacklist
| .[] | select( . as $in | $blacklist | index($in) | not) 

Или с переменной, переданной из командной строки (jq --argjson blacklist ...):

.[] | select( . as $in | $blacklist | index($in) | not) 

Чтобы сохранить структуру списка, можно использовать map( select( ...) ).

С jq 1.5 или новее вы также можете использовать any или all, например.

def except(blacklist):
  map( select( . as $in | blacklist | all(. != $in) ) );

Особый случай: струны

См., Например, Выбрать записи на основе нескольких значений в jq

person peak    schedule 15.06.2017
comment
как вы здесь используете any? Можете привести пример? - person deitch; 15.06.2017
comment
К вашему сведению, я сделал следующее: def inarray($val;ary): ary | any(. == $val); def notinarray($val;ary): ary | all(. != $val); - person deitch; 15.06.2017
comment
Ага! Оператор -! Спасибо @peak. Итак, - эквивалентно не в И не в c? - person deitch; 16.06.2017
comment
Что делать для варианта - (безусловно, самого простого), если входной массив является массивом, например [{"val":"a"},{"val":"b"},{"val":"c"},{"val":"d"},{"val":"e"}], и вы хотите отфильтровать по .val - ["a","c"] (что не работает)? - person deitch; 16.06.2017
comment
@deitch - я предлагаю вам создать новый вопрос SO. - person peak; 16.06.2017

Я уверен, что это не самое простое решение, но оно работает :)

$ echo '["a","b","c","d","e"]' | jq '.[] | select(test("[^ac]"))'

Edit: еще одно решение - это еще хуже :)

$ echo '["a","b","c","d","e"]' | jq '.[] | select(. != ("a") and . != ("b"))'
person Picard    schedule 15.06.2017
comment
Использование регулярного выражения - хорошая идея, но на самом деле это всего лишь простой пример. Я сравниваю с массивом элементов. Я бы хотел, чтобы были только одиночными символами. - person deitch; 15.06.2017
comment
@deitch: вы все еще можете использовать test, просто инвертируйте результат с помощью not, например: test("^(abc|bcd)$") | not - person Thor; 15.06.2017
comment
@ Тор, это интересно. Могу ли я сделать это с помощью переменной, например js --arg match "abc" '.[] | select(test("^($match)$") | not? - person deitch; 15.06.2017
comment
@Picard, я изначально сделал это с вашим альтернативным решением. Проблема в том, что у меня есть заранее неизвестный список предметов, с которыми можно сравнивать. - person deitch; 15.06.2017
comment
Насколько я понимаю, ваше первоначальное решение не работает, потому что оно проверяет каждую букву из входного массива с каждым элементом из списка соответствий - поэтому оно сопоставляет вход a с a из списка (нет! = Совпадение), затем a с c (да! = совпадение), поэтому он выводит вход a (хотя вы могли подумать, что этого не должно быть). Если бы это был тип элемента с набором элементов, возможно, все было бы иначе, но от того, как работают списки, я не думаю, что есть короткое решение с одним оператором. - person Picard; 15.06.2017
comment
@deitch: Да `jq --arg match 'bcd' '. [] | выберите (тест (^ (+ $ совпадение +) $) | нет) '' - person Thor; 15.06.2017
comment
В любом случае решение есть, но 2 дня не могу выложить. Ну что ж. - person deitch; 15.06.2017