Search code examples
jsonoptimizationjq

Shorten kubectl jq command


I am iterating over kubectl output that looks similar to this:

{
  "apiVersion": "v1",
  "items": [
    {
      "kind": "Pod",
      "metadata": {
        "name": "ubuntu1",
        "namespace": "development-namespace1",
      },
   ],
    {
      "kind": "Pod",
      "metadata": {
        "name": "ubuntu2",
        "namespace": "development-namespace2",
      },
   ],
}

I am trying to learn jq, but my command to get a value out of the first dictionary is really long:

kubectl get pods -o json -A | jq '.items | .[] | select(.metadata.name == "ubuntu1")  | .metadata.namespace'

How can I shorten this?


Solution

    • This answer assumes:
      • Linux version: Debian 12 bookworm
      • jq version: 1.6
      • Kubernetes version: 1.28.3

    Iterate over an array nested in a dict keyed by 'items'

    Using this (abbreviated) json output dumped from kubectl get pods -o json -A...

    {
      "apiVersion": "v1",
      "items": [
        {
          "kind": "Pod",
          "metadata": {
            "name": "ubuntu1",
            "namespace": "development-namespace1",
          },
       ],
        {
          "kind": "Pod",
          "metadata": {
            "name": "ubuntu2",
            "namespace": "development-namespace2",
          },
       ],
    }
    

    As @pmf said in a comment, you can select the value of a key nested in that array using this:

    jq '.items[] | select(.metadata.name == "ubuntu1").metadata.namespace'
    

    Explanation

    • .items[] returns the array keyed by items
    • select(.metadata.name == "ubuntu1"):
      • searches all the dicts
      • finds the dict matching the nested key: metadata.name and value: ubuntu1
    • select(.metadata.name == "ubuntu1").metadata.namespace returns value of the .metadata.namespace in the selected dictionary

    To reiterate, jq '.items[] | select(.metadata.name == "ubuntu1") returns:

    {
      "metadata": {
        "name": "ubuntu1",
        "namespace": "development-namespace1",
      }
    }
    

    Bonus material, use jq for any shell command with columns

    Parse 'ps' output

    Assume you want to parse the output of the ps command...

    Assume ps | <insert-sed-jq-pipe-here> returns the following...

    $ ps | <insert-sed-jq-pipe-here>
        PID TTY          TIME CMD
      11899 pts/0    00:00:18 bash
    2694259 pts/0    00:00:00 ps
    2694260 pts/0    00:00:00 sed
    2694261 pts/0    00:00:00 jq
    $
    

    You can convert any columnized text to json with:

    jq -sR '[sub("\n$";"") | splits("\n") | sub("^ +";"") | [splits(" +")]]'
    

    Parse 'ps' output as json

    This example will dump the aforementioned ps as json...

    $ ps | sed -ne '/PID/,$ p' | jq -sR '[sub("\n$";"") | splits("\n") | sub("^ +";"") | [splits(" +")]]'
    [
      [
        "PID",
        "TTY",
        "TIME",
        "CMD"
      ],
      [
        "11899",
        "pts/0",
        "00:00:18",
        "bash"
      ],
      [
        "2694259",
        "pts/0",
        "00:00:00",
        "ps"
      ],
      [
        "2694260",
        "pts/0",
        "00:00:00",
        "sed"
      ],
      [
        "2694261",
        "pts/0",
        "00:00:00",
        "jq"
      ]
    ]
    $
    

    Parse 'top' output as json

    You can even use this technique on top... but top looks a little tricky at first because:

    • It's interactive by default
    • You get extra text before the PID column header.... example:
    $ top -bn 1 | head -n 8
    top - 08:54:54 up 14 days,  1:13,  0 user,  load average: 0.11, 0.22, 0.30
    Tasks:  96 total,   1 running,  95 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.0 us,  6.7 sy,  0.0 ni, 93.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    MiB Mem :   3767.1 total,    109.5 free,   2140.8 used,   1990.5 buff/cache
    MiB Swap:   1024.0 total,    898.2 free,    125.8 used.   1626.4 avail Mem
    
        PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    2371290 root      20   0 1931856  48168  27024 S   6.7   1.2   0:30.64 flanneld
    $
    

    You can solve the interactive top problem with top -bn 1, which will run top once in non-interactive mode.

    Finally, that same sed we used in the ps command will strip off the lines before the top PID... so the final top command as json is:

    $ top -bn 1 | sed -ne '/PID/,$ p' | jq -sR '[sub("\n$";"") | splits("\n") | sub("^ +";"") | [splits(" +")]]'
    
    ... insert-top-json-output-here
    $