Invisible999
Invisible999

Reputation: 577

use bash string as jq filter

I don't understand what I'm doing wrong or why this does not work.

test.json file:

[
  {
    "Header": {
      "Region": "US",
      "Tenant": "Tenant1",
      "Stage": "testing",
      "ProductType": "old"
    },
    "Body": []
  },
  {
    "Header": {
      "Region": "EU",
      "Tenant": "Tenant2",
      "Stage": "development",
      "ProductType": "new"
    },
    "Body": []
  }
]

I want to display the values of the .Header.Tenant key. So the simple jq call does its job:

$ jq '[.[].Header.Tenant]' test.json
[
  "Tenant1",
  "Tenant2"
]

Now I want to assign that jq filter to a bash variable and use it with jq's --arg variable.

And I am getting this:

$ a=".[].Header.Tenant"; jq --arg xx "$a" '[$xx]' test.json
[
  ".[].Header.Tenant"
]

What is wrong?

Upvotes: 1

Views: 1449

Answers (2)

Invisible999
Invisible999

Reputation: 577

TLDR; The following code does the job:

$ a=".[].Header.Tenant"; jq -f  <(echo "[$a]") test.json
[
  "Tenant1",
  "Tenant2"
]

One as well can add/modify the filter in the jq call, if needed:

$ a=".[].Header.Tenant"; jq -f  <(echo "[$a]|length")   test.json
2

Longer explanation

My ultimate goal was to figure out how I can define the lowest common denominator jq filter in a variable and use it when calling jq, plus add additional parameters if necessary. If you have a really complex jq filter spanning multiple lines that you call frequently, you probably want to template it somehow and use that template when calling jq.

While peak demonstrated how it can be done, I think it is overengineering the simple task.

However, using process substitution combined with the jq's -f option to read a filter from the file does solve my problem.

Upvotes: 0

peak
peak

Reputation: 116730

jq does not have an eval function for evaluating arbitrary jq expressions, but it does provide functions that can be used to achieve much the same effect, the key idea being that certain JSON values can be used to specify query operations.

In your case, you would have to translate the jq query into a suitable jq operation, such as:

jq --argjson a '["Header","Tenant"]' '
   getpath(paths|select( .[- ($a|length) :]== $a))
' test.json

Extending jq's JSON-based query language

More interestingly, you could write your own eval, e.g.

jq --argjson a '[[], "Header","Tenant"]' '

  def eval($expr):
    if $expr == [] then .
    else $expr[0] as $op
    | if $op == [] then .[] | eval($expr[1:])
      else getpath([$op]) | eval($expr[1:])
      end
    end;
   eval($a)
' test.json

With eval.jq as a module

If the above def of eval were put in a file, say ~/jq/eval.jq, then you could simply write:

jq -L ~/jq --argjson a '[[], "Header","Tenant"]' '
  include "eval";
  eval($a)' test.json

Or you could specify the search path in the jq program:

jq --argjson a '[[], "Header","Tenant"]' '
  include "eval" { "search": "~/jq" };
  eval($a)' input.json

Or you could use import ...

Upvotes: 2

Related Questions