如何使用jq检查数组中是否存在元素

63

我有一个数组,需要使用jq检查该数组中是否存在元素或从该数组中获取该元素,fruit.json

{
    "fruit": [
        "apple", 
        "orange",
        "pomegranate",
        "apricot",
        "mango"
    ]
}


cat fruit.json | jq '.fruit .apple' 

无法工作

6个回答

99

'包含'的语义并不直观。一般来说,最好使用“index”来测试数组是否具有特定值,例如:

.fruit | index( "orange" )

然而,如果感兴趣的项本身是一个数组,则一般形式为:

 ARRAY | index( [ITEM] )

应该使用,例如:

[1, [2], 3] | index( [[2]] )  #=> 1

IN/1

如果你的jq有IN/1,那么更好的解决方案是使用它:

.fruit as $f | "orange" | IN($f[])
如果你的 jq 版本有 first/1(如 jq 1.5),那么这里是一个快速定义 IN/1 的方法:
def IN(s): first((s == .) // empty) // false;

any(_;_)

另一个高效且有时更为方便的替代方法是使用any/2,例如:

any(.fruit[]; . == "orange")

或者等价地说:

any(.fruit[] == "orange"; .)

2
你如何避免出现这个错误?cat fruit.json | jq '.fruit as $f | "orange" | in($f[])' => jq: error (at <stdin>:9): Cannot check whether string has a string key。我使用的是 jq 1.5.1 版本。 - Tyler Rick
似乎它与内置的 in() 不兼容(如果您可以使用,请告知您的版本)。但是,它可以使用您发布的自定义 IN(s)cat fruit.json | jq 'def IN(s): . as $in | first(if (s == $in) then true else empty end) ; .fruit as $f | "ap" | IN($f[])' => true - Tyler Rick
2
请告诉我有没有更简洁的方法来做这件事(适用于字符串值,并且不将子字符串视为匹配项)。 这些听起来很直接的函数(inhasinsidecontains - 只有像您建议的index!)中没有一个的语义是直接的... 至少当涉及到检查数组是否包含字符串这样相似的东西时... - Tyler Rick
就此而言,这个解决方案似乎可以在1.6中不需要额外的定义工作(或者至少在jqplay.org上看起来是这样)。对于看起来像是常见用例的情况来说,它仍然是一个相当长的语法。 - Cameron Stone
4
我当时非常困惑,认为函数是不区分大小写查找的,而且这个 INin 是相同的,但事实并非如此。 IN 是一种“SQL风格运算符”,与具有非常混乱语义的 in 有所不同。 - ravron

27

要求 jq 在数组 fruit 包含 "apple" 时返回成功,否则返回错误:

jq -e '.fruit|any(. == "apple")' fruit.json >/dev/null

要输出找到的元素,将其更改为

jq -e '.fruit[]|select(. == "apple")' fruit.json

如果搜索的是固定字符串,这个方法可能并不相关,但如果 select 表达式可能匹配不同的值,例如正则表达式,则此方法可能会有用。

要仅输出不同的值,请将结果传递给 unique

jq '[.fruit[]|select(match("^app"))]|unique' fruit.json

将搜索以app开头的所有水果,并输出唯一的值。(请注意,原始表达式必须用[]括起来才能传递给unique。)


关于最后一句话(“...找到的元素...”),请注意,如果.fruit是一个包含N个“apple”副本的数组,则过滤器将产生N个输出。 - peak
@peak 我怀疑只有在使用select表达式匹配不同值时才打印输出是有意义的,而且fruit数组一开始就只包含唯一的值,但没关系,我已经详细说明了如何输出不同的值。 - markusk
使用形式 a[]|select(cond)来测试数组中的元素是否满足条件本质上是低效的,除非使用某种机制在找到满足条件的元素后终止搜索。使用any/2可能是最简单的方法。 - peak
@peak 很好的观点,我在最简单的情况下只检查存在性就切换到了 any。对于更高级的情况,当调用者想要显示所有匹配的条目时,仍然需要使用 select - markusk

8
[警告:请查看评论和其他答案。]
cat fruit.json | jq '.fruit | contains(["orange"])'

13
“contains”仅要求“orange”是数组中某个元素的子字符串 - chepner

5

对于未来的访问者,如果您恰好拥有一个变量中的数组,并想检查输入是否与其匹配,并且您拥有 jq 1.5(没有 IN),您最好的选择是使用 index,但需要第二个变量:

.inputField as $inputValue | $storedArray|index($inputValue)

这在功能上相当于 .inputField | IN($storedArray[])


1

在这里进一步阐述,如果您需要将水果数组与另一个水果数组进行过滤,可以像这样进行操作:

注意:请保留HTML标签。

cat food.json | jq '[.fruit[] as $fruits | (["banana", "apple"] | contains([$fruits])) as $results | $fruits | select($results)]'

这将在上述示例JSON中仅返回一个包含“apple”的数组。

-2

这个修改后的示例在这里起作用:

jq -r '.fruit | index( "orange" )' fruit.json | tail -n 1

它只获取输出的最后一行。

如果存在,则返回0。 如果不存在,则返回null


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