2

I am following the ideas from the stackexchange articles below

but I am having problems with quotes when selecting and adding.

this is my sample json file

{
  "options": [
    {
      "label": "22",
      "value": "2022"
    },
    {
      "label": "23",
      "value": "2023"
    }
  ]
}

and this is my test bash script

#!/bin/bash

previous_year=$(date --date="$(date +'%Y') - 1 year" +%Y)
last_year=$((previous_year + 31))
label=$((last_year -2000))

echo ${last_year}

jq 'del(.options[]? | select(.label == \"${previous_year}\"))' temp.json
jq '.options += [{
      "label": '${label}',
      "value": '${last_year}'
    }]' temp.json

If I run the bash script with this sample file I get jq: error: syntax error, unexpected INVALID_CHARACTER (Unix shell quoting issues?) at , line 1: del(.options[]? | select(.label == "${previous_year}"))
jq: 1 compile error

If I remove the " it runs but don't find anything to delete because it looks without the quotes. It also add the element to the array but without quotes.

How can I do it?

  • find the element with quotes
  • add element with quotes

The expected result (what I want to achieve) is

{
  "options": [
    {
      "label": "23",
      "value": "2023"
    },
    {
      "label": "53",
      "value": "2053"
    }
  ]
}
5
  • 2
    Use --arg for the variables ... btw, your del command should select on value not label Commented Jan 6, 2023 at 0:47
  • The problem is that previous_year etc are all bash variables, not jq variables; you need to copy them into jq variables with --arg. See "Passing bash variable to jq" on stackoverflow. Commented Jan 6, 2023 at 3:32
  • I see. But isn't a way to use variables defined in the script rather then using arg passed variables? Commented Jan 6, 2023 at 7:39
  • Uh, why would you want to do that? You could create the jq query expression in double quotes and then have your variables interpolated, but this has serious robustness issues if one of the variables is empty or contains weird quoting etc. In the limit, you are opening up yourself to a "little Bobby Tables" attack. Just go with the standard solution.
    – tripleee
    Commented Jan 6, 2023 at 9:45
  • Because this is a part that will be included in a build script to update some json files used to mock some responses. Commented Jan 6, 2023 at 10:09

2 Answers 2

2

Assuming you have the correct values in the shell variables label, last_year, and previous_year and that you want to delete all entries in the options array with the value value $previous_year, and add a new entry with the label value $label and value value $last_year.

jq  --arg add_label "$label" \
    --arg add_value "$last_year" \
    --arg del_value "$previous_year" '
    del(.options[]? | select(.value == $del_value)) |
    .options += [{ label: $add_label, value: $add_value }]' file

This correctly combines the two operations in a single invocation of jq.

The command instantiates three internal jq string variables with values taken from the shell variables that you have previously created. Using --arg to create internal string variables from shell variables ensures that the values are correctly encoded (this avoids code injection vulnerabilities).

As in your code, I use .options[]? rather than just .options[] to avoid an error if the options array does not exist. If it does exist, any entry with a value value of $del_value (an internal variable with the value from the shell variable previous_year) is deleted. You tried to select using the label, but I believe this might have been a typo.

The (possibly) modified document is passed on to the next stage, which adds a new element to the options array. If the array did not previously exist, this would create it with a single element.

Also note that jq does not do in-place editing, which means you will need to redirect the output of the above command to a new name and then replace the original file with that new file. Alternatively, use GNU sponge:

jq ...as above... file | sponge file

(Assuming your file is called file.)

0
#!/bin/bash
file=temp.json
now="$(date)"
previous_year=$(date --date="$(date +'%Y') - 1 year" +%Y)
last_year=$((previous_year + 31))
label=$((last_year -2000))
echo "==============================================================================================================="
echo "| Started parsing json file at script at: $file"
echo "| Started at:                             $now"
echo "| previous year is:                       $previous_year"
echo "| last year to be at json array is:       $last_year"
echo "| label assigned to the last year :       $label"
echo "==============================================================================================================="

if cat $file | grep $previous_year
then
  jq --arg key "$previous_year" 'del(.options[] | select(.value == $key))' $file >> new.json
  jq --arg key "$label" --arg val "$last_year" '.options += [{
        "label": $key,
        "value": $val
      }]' new.json >> new2.json
  mv new2.json $file
  rm new.json
  echo "last year found and parsed"  
else
  echo "nothing to be done"
fi

now="$(date)"
echo "==============================================================================================================="
echo "| Ended script at: $now"
echo "==============================================================================================================="
´´´
2
  • As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
    – Community Bot
    Commented Jan 11, 2023 at 6:42
  • You also want to avoid the useless cat and quote "$file" everywhere.
    – tripleee
    Commented Jan 11, 2023 at 20:59

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .