74

I have an aws query that I want to filter in jq. I want to filter all the imageTags that don't end with "latest"

So far I did this but it filters things containing "latest" while I want to filter things not containing "latest" (or not ending with "latest")

aws ecr describe-images --repository-name <repo> --output json | jq '.[]' | jq '.[]' | jq "select ((.imagePushedAt < 14893094695) and (.imageTags[] | contains(\"latest\")))"

Thanks

Smar
  • 8,109
  • 3
  • 36
  • 48
Gavriel Fishel
  • 865
  • 1
  • 6
  • 10
  • Please provide a minimal, complete verifiable example (see http://stackoverflow.com/help/mcve). One reason is that your problem description is unclear as to whether it requires the use of `any` (or equivalent). – peak Mar 12 '17 at 13:54
  • Possible duplicate of [How to filter an array of objects based on values in an inner array with jq?](http://stackoverflow.com/questions/26701538/how-to-filter-an-array-of-objects-based-on-values-in-an-inner-array-with-jq) – Gavriel Fishel Mar 12 '17 at 14:44

4 Answers4

117

You can use not to reverse the logic

(.imageTags[] | contains(\"latest\") | not)

Also, I'd imagine you can simplify your pipeline into a single jq call.

J. Doe
  • 1,367
  • 1
  • 9
  • 2
  • Hey, thanks for answering, I tried to run it but I get: aws ecr describe-images --repository-name embed-service --output json | jq '.[]' | jq '.[]' | jq "select (.imageTags[] | contains(\"latest\") | not)" jq: error: Cannot iterate over null But I do have imageTags without "latest" – Gavriel Fishel Mar 12 '17 at 14:40
  • How strange. Any chance you can give an example of the JSON you're dealing with? – J. Doe Mar 12 '17 at 19:11
  • 1
    The mental gap I was missing is that `contains()` is a terminal filter that just returns `true` or `false`. To invert it just pipe it to `not`! It's so obvious in hindsight... – mgalgs Oct 10 '19 at 16:36
  • @GavrielFishel: that message means that some of the images don't have tags. Try this to fix it "aws ecr describe-images --repository-name embed-service --output json | jq '.[]?' | jq '.[]?' | jq "select (.imageTags[]? | contains(\"latest\") | not)" – user3630264 Oct 02 '20 at 18:34
  • 1
    Just note that if you are doing multiple evaluations this has to come in the same pipeline you're reversing. `.stuff[] | contains("thing") | not`, not `(.stuff[] | contains("thing")) | not`. – Chaim Eliyah May 05 '21 at 16:57
6

All you have to do is | not within your jq

A useful example, in particular for mac brew users:

List all bottled formulae

by querying the JSON and parsing the output

brew info --json=v1 --installed | jq -r 'map(
    select(.installed[].poured_from_bottle)|.name) | unique | .[]' | tr '\n' ' '

List all non-bottled formulae

by querying the JSON and parsing the output and using | not

brew info --json=v1 --installed | jq -r 'map(                                                                                                                          
  select(.installed[].poured_from_bottle | not) | .name) | unique | .[]'
bschlueter
  • 3,817
  • 1
  • 30
  • 48
jasonleonhard
  • 12,047
  • 89
  • 66
5

In this case contains() doesn't work properly, is better use the not of index() function

select(.imageTags | index("latest") | not)
greybeard
  • 2,249
  • 8
  • 30
  • 66
4

This .[] | .[] can be shorten to .[][] e.g.,

$ jq --null-input '[[1,2],[3,4]] | .[] | .[]'
1
2
3
4
$ jq --null-input '[[1,2],[3,4]] | .[][]'
1
2
3
4

To check whether a string does not contain another string, you can combine contains and not e.g.,

$ jq --null-input '"foobar" | contains("foo") | not'
false
$ jq --null-input '"barbaz" | contains("foo") | not'
true

You can do something similar with an array of strings with either any or all e.g.,

$ jq --null-input '["foobar","barbaz"] | any(.[]; contains("foo"))'
true
$ jq --null-input '["foobar","barbaz"] | any(.[]; contains("qux"))'
false
$ jq --null-input '["foobar","barbaz"] | all(.[]; contains("ba"))'
true
$ jq --null-input '["foobar","barbaz"] | all(.[]; contains("qux"))'
false

Say you had file.json:

[ [["foo", "foo"],["foo", "bat"]]
, [["foo", "bar"],["foo", "bat"]]
, [["foo", "baz"],["foo", "bat"]]
]

And you only want to keep the nested arrays that don't have any strings with "ba":

$ jq --compact-output '.[][] | select(all(.[]; contains("bat") | not))' file.json
["foo","foo"]
["foo","bar"]
["foo","baz"]
customcommander
  • 17,580
  • 5
  • 58
  • 84