0

I am doing a curl call to a server from a bash script. In the response I want to extract some data using jq. but, I am getting below error from jq

jq: error (at <stdin>:1): Cannot index number with string "_links"

Part of script:

#!/bin/bash

set -e

keyword="config"

response=$(curl --location "${HEADERS[@]}" -s -w "%{http_code}\n" -X GET "$url")

if echo ${response} | grep "$keyword" ; then
    extract=$(jq -r "._links" <<< "${response}")
    echo "$extract"
fi

The actual response:

{
    "config": {
        "polling": {
            "sleep": "00:20:00"
        }
    },
    "_links": {
        "deployment": {
            "href": "https://www.example.com"
        },
        "configData": {
            "href": "https://www.example.com/configData"
        }
    }
}
200

Can anyone please let me know what is the issue here?

Thanks in advance

P.S: I am testing on Ubuntu (on WSL). please let me know if any info is missing

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
Preeti
  • 535
  • 1
  • 6
  • 30
  • 2
    Works for me when I put the response in a file and run `jq -r "._links" < response.json`. It prints everything under "_links". Can you make a [mcve] without the (undefined) url? – Robert Apr 05 '23 at 14:47
  • 2
    The `200` is a problem. It's a number. `.config` or `._links` doesn't make sense with a number. – Charles Duffy Apr 05 '23 at 15:19
  • 1
    BTW, `set -e` [is a bad idea](https://mywiki.wooledge.org/BashFAQ/105). At minimum, review the exercises section of the linked FAQ; it's much more reliable to put `|| exit` or `|| return`, as appropriate, after each command that you want to cause an abort on failure. – Charles Duffy Apr 05 '23 at 15:20
  • @CharlesDuffy yea that's correct. This was just a initial version of my script. It will be updated. Thanks for pointing out! – Preeti Apr 05 '23 at 15:25
  • 1
    BTW, think about `if [[ $response = *keyword* ]]` instead of the `echo | grep` pipeline. Avoids bugs like those described in [I just assigned a variable, but `echo $variable` shows something else](https://stackoverflow.com/questions/29378566/i-just-assigned-a-variable-but-echo-variable-shows-something-else), and if your document isn't huge it'll be quite a lot faster since it doesn't need to start a separate grep executable. – Charles Duffy Apr 05 '23 at 15:32

2 Answers2

1

Your variable response does not contain what you have shown. The actual content of the variable is:

{
    "config": {
        "polling": {
            "sleep": "00:20:00"
        }
    },
    "_links": {
        "deployment": {
            "href": "https://www.example.com"
        },
        "configData": {
            "href": "https://www.example.com/configData"
        }
    }
}
200

That 200 is added to the output because you call curl -w "%{http_code}\n", which will add the response status code to the output. If you plan on processing the output, get rid of the -w option (you are not parsing it with jq, so you don't need it).

If you require 200 to be part of the output, then you could instruct jq to read the full input into a single array and only operate on the first item:

jq -s 'first._link' <<< "$response"

But in that case, I recommend going with Charles Duffy's answer and use -n in combination with input.

knittl
  • 246,190
  • 53
  • 318
  • 364
  • Hello @knittl, thanks for the update. Yes, that was the issue. But I don't want to get rid of `-w "%{http_code}\n"`. This will be used later to check http status... – Preeti Apr 05 '23 at 15:19
  • @CharlesDuffy yes not by `jq`. For example to check if response code is 200 before doing `jq` – Preeti Apr 05 '23 at 15:21
  • You could, if you chose, make the jq be the thing that processes the 200. Otherwise you end up doing something relatively messy like running a separate copy of `tail`. – Charles Duffy Apr 05 '23 at 15:23
1

If your input has a JSON document followed by other content, you can make your jq only read that one input:

extract=$(jq -n "input | ._links" <<<"$response")

The -n tells jq not to read anything by default; the -n, used exactly once, tells it to read one thing.


You could also extract both the _links attribute and the HTTP status code with just one invocation of jq:

IFS=$'\n' read -r -d '' extract http_code < <(
  jq -nc '
    input as $full_doc | input as $status_code |
    (($full_doc | ._links), $status_code)
  ' <<<"$response" && printf '\0'
)

Using read -d '' causes read to fail if it doesn't find a NUL at the end of input, and the && printf '\0' adds a NUL at the end of the process substitution's output if jq reports success.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Thanks @CharlesDuffy. `extract=$(jq -n "input | ._links" <<<"$response")` adds quotes `""` to the output..But adding `| tr -d '"'` worked – Preeti Apr 05 '23 at 15:35
  • 2
    @Preeti use `-r` if you don't want the quotes when evaluating to a string, but the format of your sample input JSON says `._links` is not a string but instead a dictionary/map/object, so `-r` won't do anything unless your real JSON format is different from the one you showed us. The other thing is that `-r` is a bit of a security risk in the case where we use `-c` and assume single-line output for each entry. – Charles Duffy Apr 05 '23 at 19:00
  • @Preeti (are you really extracting something different, like `._links.deployment.href`, but just shortened for the question? When you said just `._links`, that has a _meaning_ in the context of your sample data, which is why it appeared that `-r` was being used unnecessarily/inappropriately). – Charles Duffy Apr 05 '23 at 19:09
  • You can do `jq -nc 'input|._links, input'` – Philippe Apr 05 '23 at 23:06