How to determine if a YAML document contains a map that has one key but not another key with yq?

82 views Asked by At

I would like to search recursively through all .yaml files in a directory and retrieve the paths to files with this extension that meet some requirements.

I want to get all files that match this requirement: The file contains a RunningProcess map that contains a substring|export key but no CmdLine|regex key. The RunningProcess map can occur anywhere in the YAML tree, but the other two keys must be immediate children of the RunningProcess map.

I am using yq version 4.35.2.

My terminology might not properly match the terms used in the official YAML specification, so I provide some examples below.

Examples

Example 1: The following YAML file should be included in the results:

RunningProcess:
  substring|export: true
  otherKey: true
OtherKey: {}

Example 2: The following YAML file should not be included in the results, since it contains the CmdLine|regex key:

RunningProcess:
  substring|export: true
  CmdLine|regex: true
OtherKey: {}

Example 3: The following YAML file should not be included in the results, since it does not contain a RunningProcess map:

OtherKey: {}

Example 4: The following YAML should be included in the results:

Outer:
  RunningProcess:
    substring|export: true
    otherKey: true
OtherKey: {}

What I have tried

I know how to write a loop with find to try each file at a time (as it seems yq does not support recursive searching), so I was focusing on the yq query.

I tried the query below, but I get an error message:

$ yq eval 'select(has("RunningProcess") and .RunningProcess | has("substring|export") and not .RunningProcess | has("CmdLine|regex"))' test_file.yaml
Error: bad expression, please check expression syntax
1

There are 1 answers

2
pmf On

not is a regular filter, use it like … | not

.RunningProcess | (has("substring|export") and (has("CmdLine|regex") | not))

With the -e flag, yq provides a way to transform its output into an exit status. From the manual:

-e, --exit-status set exit status if there are no matches or null or false is returned

Thus, it suffices to take the boolean values from has and not, and there's no need to select the content (unless you want to further process it). Here's one example using this flag in combination with an if statement in the shell, and on a file stored in the shell variable $file. stdout and stderr are suppressed to not interfere.

filter='.RunningProcess | (has("substring|export") and (has("CmdLine|regex") | not))'

if yq -e "$filter" "$file" &>/dev/null
  then echo "$file matches"
# else echo "$file does not match"
fi

The RunningProcess map can occur anywhere in the YAML document (it doesn't need to be in the root), but the other two keys must be immediate children of the RunningProcess map.

Then traverse down the document using .., and do the above if there's any match: [.. | …] | any. Here's the full filter:

[.. | .RunningProcess | (has("substring|export") and (has("CmdLine|regex") | not))] | any