Home > Software design >  jq output is empty when tag name does not exist
jq output is empty when tag name does not exist

Time:12-16

When I run the jq command to parse a json document from the amazon cli I have the following problem.

I’m parsing through the IP address and a tag called "Enviroment". The enviroment tag in the instance does not exist therefore it does not throw me any result.

Here's an example of the relevant output returned by the AWS CLI

{
  "Reservations": [
    {
      "Instances": [
        {
          "PrivateIpAddress": "10.0.0.1",
          "Tags": [
            {
              "Key": "Name",
              "Value": "Balance-OTA-SS_a"
            },
            {
              "Key": "Environment",
              "Value": "alpha"
            }
          ]
        }
      ]
    },
    {
      "Instances": [
        {
          "PrivateIpAddress": "10.0.0.2",
          "Tags": [
            {
              "Key": "Name",
              "Value": "Balance-OTA-SS_a"
            }
          ]
        }
      ]
    }
  ]
}

I’m running the following command

aws ec2 describe-instances --filters "Name=tag:Name,Values=Balance-OTA-SS_a" | jq -c '.Reservations[].Instances[] | ({IP: .PrivateIpAddress, Ambiente: (.Tags[]|select(.Key=="Environment")|.Value)})'

## output
empty

How do I show the IP address in the output of the command even if the enviroment tag does not exist?

Regards,

CodePudding user response:

You can either use an if ... then ... else ... end construct, or //. For example:

.Reservations[].Instances[]
| {IP: .PrivateIpAddress}   
  ({Ambiente: (.Tags[]|select(.Key=="Environment")|.Value)}
   // null)

CodePudding user response:

Let's assume this input:

{
  "Reservations": [
    {
      "Instances": [
        {
          "PrivateIpAddress": "10.0.0.1",
          "Tags": [
            {
              "Key": "Name",
              "Value": "Balance-OTA-SS_a"
            },
            {
              "Key": "Environment",
              "Value": "alpha"
            }
          ]
        }
      ]
    },
    {
      "Instances": [
        {
          "PrivateIpAddress": "10.0.0.2",
          "Tags": [
            {
              "Key": "Name",
              "Value": "Balance-OTA-SS_a"
            }
          ]
        }
      ]
    }
  ]
}

This is the format returned by describe-instances, but with all the irrelevant fields removed.

Note that tags is always a list of objects, each of which has a Key and a Value. This format is perfect for from_entries, which can transform this list of tags into a convenient mapping object. Try this:

.Reservations[].Instances[] |
{
  IP: .PrivateIpAddress,
  Ambiente: (.Tags|from_entries.Environment)
}
{"IP":"10.0.0.1","Ambiente":"alpha"}
{"IP":"10.0.0.2","Ambiente":null}

That answers how to do it. But you probably want to understand why your approach didn't work.

.Reservations[].Instances[] |
{
  IP: .PrivateIpAddress,
  Ambiente: (.Tags[]|select(.Key=="Environment")|.Value)
}

The .[] filter you're using on the tags can return zero or multiple results. Similarly, the select filter can eliminate some or all items. When you apply this inside an object constructor (the expression from { to }), you're causing that whole object to be created a variable number of times. You need to be very careful where you use these filters, because often that's not what you want at all. Often you instead want to do one of the following:

  • Wrap the expression that returns multiple results in an array constructor [ ... ]. That way instead of outputting the parent object potentially zero or multiple times, you output it once containing an array that potentially has zero or multiple items. E.g.
    [.Tags[]|select(.Key=="Environment")]
    
  • Apply map to the array to keep it an array but process its contents, e.g.
    .Tags|map(select(.Key=="Environment"))
    
  • Apply first(expr) to capture only the first value emitted by the expression. If the expression might emit zero items, you can use the comma operator to provide a default, e.g.
    first((.Tags[]|select(.Key=="Environment")),null)
    
  • Apply some other array-level function, such as from_entries.
    .Tags|from_entries.Environment
    
  • Related