more efficient way to parse kubenetes pod name - json

I have the following code which works but the | . [ ] section looks redundant.
Can anyone offer a suggestion as to how I could remove that code - preferably with an explanation as to why it's ineficient?
kubectl get pods -l app.kubernetes.io/name=esl-mops -o json |\
jq -r '[.items[] | {name:.metadata.name} ] | . [ ] .name'

If you're saying that your intuition is telling you that there's a simpler and more efficient way to get the .metadata.name values, then lucky you, because the pipeline could be streamlined to just:
.items[] | .metadata.name
This avoids all the overhead of constructing an array, constructing an object, and then dismantling everything.
By the way, this illustrates quite nicely the value of jq being stream-oriented. This often helps make it possible to have simple solutions to simple problems, and to achieve an efficient solution if there is one.

Related

How to use jq to extract a particular field from a terraform plan to show resources that are updated or changed?

I just want to be able to have a small quick view or list of what is changing with a terraform plan instead of the long output given by a terraform plan.
So far I think it can be done with a terraform plan and jq.
Here is what I have so far -
I run a plan like this:
terraform plan -out=tfplan -no-color -detailed-exitcode
Then I am trying to use jq to get the changes or updates using this:
terraform show -json tfplan | jq '.resource_changes[]
| select( .change.actions
| contains("create") or contains("update") )'
It gives me the error :
jq: error (at <stdin>:1): array (["no-op"]) and string ("create")
cannot have their containment checked
My jq skills are not the best - can anyone update my jq to work or is there an alternative way to do this?
contains checks if one array is a subarray of the other, recursively (substrings are matched too; note the "d" in "create" vs "created"):
$ jq -n '["created"] | contains(["create"])'
true
You can use the SQL-style IN filter:
$ jq -n '"create" | IN("created", "foo")'
false
$ jq -n '"created" | IN("created", "bar")'
true
So for your concrete use case you would probably want something like the following:
terraform show -json tfplan | jq '
.resource_changes[]
| select(
.change.actions as $actions
| "create" | IN($actions[])
or "update" | IN($actions[]))'
Or using any/2:
terraform show -json tfplan | jq '
.resource_changes[]
| select(any(.change.actions[]; .=="create" or .=="update"))'

using jq to display pretty json and grep at the output to filter values

On linux, i actually use jq associated with a curl command.
Something like this..
curl <curl expression> | jq
but my pretty json at output got two many lines.
I'd like to filter rows, with something like this :
curl <curl expression> | jq | grep myfilter
jq util do not accept to be grepped.
How can i do the work ?
If you really want to use grep, try jq . instead of just jq. Some older versions of jq in effect did the wrong thing under some circumstances when no filter was specified.

jq group_by does not play nice with .[]

I have a json file locally called pokemini.json. These are the contents of it;
{"name":"Bulbasaur","type":["Grass","Poison"],"total":318,"hp":45,"attack":49}
{"name":"Ivysaur","type":["Grass","Poison"],"total":405,"hp":60,"attack":62}
{"name":"Venusaur","type":["Grass","Poison"],"total":525,"hp":80,"attack":82}
{"name":"VenusaurMega Venusaur","type":["Grass","Poison"],"total":625,"hp":80,"attack":100}
{"name":"Charmander","type":["Fire"],"total":309,"hp":39,"attack":52}
{"name":"Charmeleon","type":["Fire"],"total":405,"hp":58,"attack":64}
{"name":"Charizard","type":["Fire","Flying"],"total":534,"hp":78,"attack":84}
{"name":"CharizardMega Charizard X","type":["Fire","Dragon"],"total":634,"hp":78,"attack":130}
{"name":"CharizardMega Charizard Y","type":["Fire","Flying"],"total":634,"hp":78,"attack":104}
{"name":"Squirtle","type":["Water"],"total":314,"hp":44,"attack":48}
There are a few types of pokemon in here and I want to do some aggregation with jq.
I could, per example, write this command;
> jq -s -c 'group_by(.type[0]) | .[]' pokemini.json
[{"name":"Charmander","type":["Fire"],"total":309,"hp":39,"attack":52},{"name":"Charmeleon","type":["Fire"],"total":405,"hp":58,"attack":64},{"name":"Charizard","type":["Fire","Flying"],"total":534,"hp":78,"attack":84},{"name":"CharizardMega Charizard X","type":["Fire","Dragon"],"total":634,"hp":78,"attack":130},{"name":"CharizardMega Charizard Y","type":["Fire","Flying"],"total":634,"hp":78,"attack":104}]
[{"name":"Bulbasaur","type":["Grass","Poison"],"total":318,"hp":45,"attack":49},{"name":"Ivysaur","type":["Grass","Poison"],"total":405,"hp":60,"attack":62},{"name":"Venusaur","type":["Grass","Poison"],"total":525,"hp":80,"attack":82},{"name":"VenusaurMega Venusaur","type":["Grass","Poison"],"total":625,"hp":80,"attack":100}]
[{"name":"Squirtle","type":["Water"],"total":314,"hp":44,"attack":48}]
I am aware that the -c flag is what is causing it to print line by line and that I need -s to handle the fact that my json file is more like jsonlines that actualy json. It should also be pointed that out there are only three types of pokemon detected because I can grouping over .type[0] (note that [0]).
I don't get why this does not work though;
> jq -s '.[] | group_by(.type[0])' pokemini.json
jq: error (at pokemini.json:10): Cannot index string with string "type"
group_by/1 expects its input to be an array. By calling .[] first, you are effectively undoing the work of the -s option.
By the way, an alternative to using -s is to use inputs with the -n command-line option, but in this case it makes little difference. When you don’t actually need to read all the entire stream of inputs at once, though, using inputs is in general more efficient.

I cannot get jq to give me the value I'm looking for.

I'm trying to use jq to get a value from the JSON that cURL returns.
This is the JSON cURL passes to jq (and, FTR, I want jq to return "VALUE-I-WANT" without the quotation marks):
[
{
"success":{
"username":"VALUE-I-WANT"
}
}
]
I initially tried this:
jq ' . | .success | .username'
and got
jq: error (at <stdin>:0): Cannot index array with string "success"
I then tried a bunch of variations, with no luck.
With a bunch of searching the web, I found this SE entry, and thought it might have been my saviour (spoiler, it wasn't). But it led me to try these:
jq -r '.[].success.username'
jq -r '.[].success'
They didn't return an error, they returned "null". Which may or may not be an improvement.
Can anybody tell me what I'm doing wrong here? And why it's wrong?
You need to pipe the output of .[] into the next filter.
jq -r '.[] | .success.username' tmp.json
tl;dr
# Extract .success.username from ALL array elements.
# .[] enumerates all array elements
# -r produces raw (unquoted) output
jq -r '.[].success.username' file.json
# Extract .success.username only from the 1st array element.
jq -r '.[0].success.username' file.json
Your input is an array, so in order to access its elements you need .[], the array/object-value iterator (as the name suggests, it can also enumerate the properties of an object):
Just . | sends the input (.) array as a whole through the pipeline, and an array only has numerical indices, so the attempt to index (access) it with .success.username fails.
Thus, simply replacing . | with .[] | in your original attempt, combined with -r to get raw (unquoted output), should solve your problem, as shown in chepner's helpful answer.
However, peak points out that since at least jq 1.3 (current as of this writing is jq 1.5) you don't strictly need a pipeline, as demonstrated in the commands at the top.
So the 2nd command in your question should work with your sample input, unless you're using an older version.

How to add a header to CSV export in jq?

I'm taking a modified command from the jq tutorial:
curl 'https://api.github.com/repos/stedolan/jq/commits?per_page=5' \
| jq -r -c '.[] | {message: .commit.message, name: .commit.committer.name} | [.[]] | #csv'
Which does csv export well, but missing the headers as the top:
"Fix README","Nicolas Williams"
"README: send questions to SO and Freenode","Nicolas Williams"
"usage() should check fprintf() result (fix #771)","Nicolas Williams"
"Use jv_mem_alloc() in compile.c (fix #771)","Nicolas Williams"
"Fix header guards (fix #770)","Nicolas Williams"
How can I add the header (in this case message,name) at the top? (I know it's possible manually, but how to do it within jq?)
Just add the header text in an array in front of the values.
["Commit Message","Committer Name"], (.[].commit | [.message,.committer.name]) | #csv
Based on Anton's comments on Jeff Mercado's answer, this snippet will get the key names of the properties of the first element and output them as an array before the rows, thus using them as headers. If different rows have different properties, then it won't work well; then again, neither would the resulting CSV.
map({message: .commit.message, name: .commit.committer.name}) | (.[0] | to_entries | map(.key)), (.[] | [.[]]) | #csv
While I fully realize OP was looking for a purely jq answer, I found this question looking for any answer. So, let me offer one I found (and found useful) to others like me.
sudo apt install moreutils - if you don't have them yet. Moreutils website.
echo "Any, column, name, that, is, not, in, your, json, object" | cat - your.csv | sponge your.csv
Disadvantages: requires moreutils package, is not just jq-reliant, so some would understandably say less elegant.
Advantages: you choose your headers, not your JSON keys. Also, pure jq ways are bothered by the sorting of the keys, depending on your version.
How does it work?
echo outputs your header
cat - takes echo output from stdin (cause -) and conCATenates it with your csv file
sponge waits until that is done and writes the result to same file, overwriting it.
But you could do it with tee without having to install any packages!
No, you could not, as Kos excellently demonstrates here. Not unless you're fine with loosing your csv at some point.