获取 jq json 解析中的第一个(或者不是 h)元素

我可以在[]中得到 json 中的第一个元素

$ echo '[{"a":"x", "b":true}, {"a":"XML", "b":false}]' | jq '.[1]'
{
"a": "XML",
"b": false
}

但是如果 json 已经被反汇编(例如,在使用‘ select’过滤条目之后) ,我如何选择一个条目并避免这里看到的错误呢?

$ echo '[{"a":"x", "b":true}, {"a":"x", "b":false},{"a":"XML", "b":false}]' | jq '.[] | select( .a == "x")'
{
"a": "x",
"b": true
}
{
"a": "x",
"b": false
}
$ echo '[{"a":"x", "b":true}, {"a":"x", "b":false},{"a":"XML", "b":false}]' | jq '.[] | select( .a == "x") | .[1]'
jq: error (at <stdin>:1): Cannot index object with number
131657 次浏览

use map

cat raw.json|jq -r -c 'map(select(.a=="x"))|.[1]'

map receives a filter to filter an array.

this command

cat raw.json|jq -r -c 'map(select(.a=="x"))'

give the middle result

[{"a":"x","b":true},{"a":"x","b":false}]

.[1] take the first element

You can wrap the results from select in an array:

jq '[.[]|select(.a=="x")][0]' your.json

Output:

{
"a": "x",
"b": false
}

jq also provides first/0, last/0, nth/1 so in this case the filter

  ( map(select(.a == "x")) | first  )
, ( map(select(.a == "x")) | last   )
, ( map(select(.a == "x")) | nth(1) )

produces

{
"a": "x",
"b": true
}
{
"a": "x",
"b": false
}
{
"a": "x",
"b": false
}

Additional streaming forms 'first/1', 'last/1' and 'nth/2' are also available so with this data

  ( first(.[]  | select(.a == "x")) )
, ( last(.[]   | select(.a == "x")) )
, ( nth(1; .[] | select(.a == "x")) )

produces

{
"a": "x",
"b": true
}
{
"a": "x",
"b": false
}
{
"a": "x",
"b": false
}

Many of the previous answers work by avoiding to create a stream of objects in the first place. But what if you are starting with a stream of objects—for example JSON-formatted application logs? If your input file (or stream) has multiple objects in it, all you need to do to choose a single entry from a stream of independent objects is to use the --slurp option (or -s for the short form):

   o   --slurp/-s:


Instead of running the filter for each JSON object in the input, read
the entire input stream into a large array and run the filter just once.

Then you can just index into the array. For example, to get second item (with index 1):

jq --slurp '.[1]'

Putting this together with your original question, where you want to select that item from a stream:

echo '{"a":"x", "b":true} {"a":"XML", "b":false}' | jq --slurp '.[1]'

which results in this output:

{
"a": "XML",
"b": false
}

I have the same scenario as jbyler - I want to parse N lines of a JSON log, where each line is a single object:

host ~ # head /var/log/nginx/access.log.json | cut -c 1-80
{"remote_addr":"127.0.0.1","remote_port":"47700","time_iso8601":"2022-08-31T00:0
{"remote_addr":"127.0.0.1","remote_port":"35576","time_iso8601":"2022-08-31T00:0
{"remote_addr":"127.0.0.1","remote_port":"47708","time_iso8601":"2022-08-31T00:0
{"remote_addr":"127.0.0.1","remote_port":"52974","time_iso8601":"2022-08-31T00:0
{"remote_addr":"127.0.0.1","remote_port":"52976","time_iso8601":"2022-08-31T00:0
{"remote_addr":"127.0.0.1","remote_port":"51414","time_iso8601":"2022-08-31T00:0
{"remote_addr":"127.0.0.1","remote_port":"41942","time_iso8601":"2022-08-31T00:1
{"remote_addr":"127.0.0.1","remote_port":"41946","time_iso8601":"2022-08-31T00:1
{"remote_addr":"127.0.0.1","remote_port":"37982","time_iso8601":"2022-08-31T00:1
{"remote_addr":"127.0.0.1","remote_port":"56602","time_iso8601":"2022-08-31T00:1

I do not like the --slurp solution because this will cause jq to read the entire file, when I might only be interested in the first N lines.

The solution for this seems to be the input function combined with jq -n, which reads a single input object:

host ~ # cat /var/log/nginx/access.log.json | jq -r -n 'input | [.remote_addr, .remote_port, .time_iso8601] | @tsv'
127.0.0.1       47700   2022-08-31T00:02:53+02:00

Combined with range, I can use it to read up to N input objects:

host ~ # cat /var/log/nginx/access.log.json | jq -r -n 'range(10) as $i | input | [.remote_addr, .remote_port, .time_iso8601] | @tsv'
127.0.0.1       47700   2022-08-31T00:02:53+02:00
127.0.0.1       35576   2022-08-31T00:02:53+02:00
127.0.0.1       47708   2022-08-31T00:02:53+02:00
127.0.0.1       52974   2022-08-31T00:07:53+02:00
127.0.0.1       52976   2022-08-31T00:07:53+02:00
127.0.0.1       51414   2022-08-31T00:07:58+02:00
127.0.0.1       41942   2022-08-31T00:12:53+02:00
127.0.0.1       41946   2022-08-31T00:12:53+02:00
127.0.0.1       37982   2022-08-31T00:13:03+02:00
127.0.0.1       56602   2022-08-31T00:17:53+02:00

Be careful with wrapping this into an array though - doing it wrong will cause jq to reevaluate the [range(10) as $i | input] expression, so here I'm not getting elements [0, 1, 2], but [0, 11, 22]:

# WRONG, DON'T DO THIS
host ~ # cat /var/log/nginx/access.log.json | jq -r -n '[range(10) as $i | input][0,1,2] | [.remote_addr, .remote_port, .time_iso8601] | @tsv'
127.0.0.1       47700   2022-08-31T00:02:53+02:00
127.0.0.1       59784   2022-08-31T00:18:22+02:00
127.0.0.1       34316   2022-08-31T00:37:53+02:00

To safely access the same array multiple times, you need to have a pipe in between:

host ~ # cat /var/log/nginx/access.log.json | jq -r -n '[range(10) as $i | input] | .[0,1,2] | [.remote_addr, .remote_port, .time_iso8601] | @tsv'
127.0.0.1       47700   2022-08-31T00:02:53+02:00
127.0.0.1       35576   2022-08-31T00:02:53+02:00
127.0.0.1       47708   2022-08-31T00:02:53+02:00

This way, you can take up to N objects from the input file, and then extract arbitrary ones from this selection.