如何使用jq过滤出不在列表中的选择项?

19
在jq中,我可以相对轻松地选择列表中的一个项:

In jq中,我可以相对轻松地选择一个列表中的项:in

$ 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"

如何过滤掉匹配的项?


那真是非常痛苦,但我终于成功了。 - deitch
1
你的过滤器实际上与 . != "a" or . != "c" 相同。当然,这总是为真,所以你没有看到任何被过滤的内容。但是,由于你使用了逗号运算符,现在你会得到重复的值。请记住,对于从逗号产生的每个值,表达式都将重新计算新值。因此,select(. != ("a","c")) 变成了 select(. != "a"), select(. != "c")。那么现在应该很清楚发生了什么。 - Jeff Mercado
感谢@JeffMercado的解释。我一直搞不明白为什么它不起作用。实际上,. != ("a","c")是逻辑或,而我期望的是逻辑与(即使. == ("a","c")是逻辑或)。 - deitch
不完全是这样。更像是 ("a","c") 是两个值 "a""c"。对于使用它的任何表达式,复制该表达式并将值 "a""c" 替换为副本。 - Jeff Mercado
2个回答

22

最简单且最健壮(针对 jq 版本)的方法是使用内置的-

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

如果黑名单非常长且存在重复,那么可能需要将它们删除(例如使用unique)。

变体

在jq 1.4及以上版本中,也可以使用indexnot来解决该问题,例如:

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

或者,通过从命令行传递变量(jq --argjson黑名单...):

.[] | select( . as $in | $blacklist | index($in) | not) 
为了保留列表结构,可以使用 map( select( ...) )
在 jq 1.5 或更高版本中,也可以使用 anyall,例如:
def except(blacklist):
  map( select( . as $in | blacklist | all(. != $in) ) );

特殊情况:字符串

详见例如jq中基于多个值选择条目


你如何在这里使用 any?你能分享一个例子吗? - deitch
FYI,我所做的是:def inarray($val;ary): ary | any(. == $val); def notinarray($val;ary): ary | all(. != $val); - deitch
啊哈!是减号操作符!谢谢@peak。所以“-”相当于“不在“a”中且不在“c”中”? - deitch
对于“-”变体(迄今为止最简单的),如果输入是一个数组,例如 [{"val":"a"},{"val":"b"},{"val":"c"},{"val":"d"},{"val":"e"}],并且您想要按 .val - ["a","c"] 进行过滤(这不起作用),那么该怎么办? - deitch
@deitch - 我建议您创建一个新的SO问题。 - peak
使用 jq 1.6 或更高版本,可以使用 INecho '["a","b","c","d","e"]' | jq '.[] | select(. | IN("a", "c") | not)' - gobenji

3

我相信这不是最简单的解决方案,但它可行 :)

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

编辑:还有一种解决方案-这甚至更糟糕 :)

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

使用正则表达式是个好主意,但这只是一个简单的示例。我正在与一组项目进行比较。我希望它只是单个字符。 - deitch
@deitch:你仍然可以使用 test,只需使用 not 反转结果,例如:test("^(abc|bcd)$") | not - Thor
@Thor 那很有趣。我可以用变量来实现吗,例如 js --arg match "abc" '.[] | select(test("^($match)$") | not - deitch
@Picard,我最初使用了您的替代方案。问题是我有一个未知的列表需要与之匹配。 - deitch
据我所知,您的初始解决方案无法正常工作,因为它会将输入数组中的每个字母与匹配列表中的每个项进行比较 - 因此它将输入的 "a" 与列表中的 "a" 进行匹配(不是 != 匹配),然后将 "a" 与 "c" 进行匹配(是 != 匹配),因此输出输入的 "a"(尽管您认为它不应该)。如果它是“元素集”元素类型,也许情况会有所不同,但从列表的工作方式来看,我认为没有一个简短的、单一的运算符解决这个问题。 - Picard
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接