I want to change only a certain section of a file that looks like this:
{
"Section_A": {
"ws/abc-Location01": 24,
"ws/abc-Location02": 67,
"ws/abc-Location03: 101,
},
"Section_B": {
"ws/abc-Location01": 33,
"ws/abc-Location02": 59,
"ws/abc-Location03: 92,
}
}
I want to change ONLY the numeric values in field 2 in Section B, based on match on the location name with an array, from a shell script.
For example, if ws/abc-Location01
is present in my array in my script, its value should not be changed, else its value should be changed to the value of the 2nd field in file1.txt, where the 1st field contains location name (like ws/abc-Location01
)
I'm not sure how to go about this, since a given location name occurs in each section. So how do I tell my script to ignore Section A, Section C and so on, and just change the value in Section B?
Using jq:
# list all the keys in Section B into a bash array
keys_str=$(jq --raw-output '.["Section_B"] | keys | @sh' <test.json)
eval "keys_arr=( $keys_str )"
# determine which keys are not present in your other array
# assumes that the other array is associative for sanity
keys_to_change=( )
for key in "${keys_arr[@]}"; do
[[ ${other_array[$key]} ]] || keys_to_change+=( "$key" )
done
for key in "${keys_to_change[@]}"; do
# this is the inefficient way to do this, but it's much less code / easier
# to show; in this case, we're changing everything listed to 0, since you
# don't specify how you want to change them more precisely.
jq --arg key "$key" \
'.["Section_B"][$key] = 0' \
<test.json >test.json.new \
&& mv test.json.new test.json
done
In general, syntax-aware tools are your best bet here -- just as folks asking similar questions about XML are told to use XMLStarlet or another XPath engine, using a proper JSON parser and generator is the Right Thing for making edits. This can require installing extra dependencies -- but it ensures that you're actually generating syntactically valid content.
Assuming your file is proper JSON (it does not seems to be the case in your sample file -- but you confirmed it in a comment), a few lines of your favorite script language should be sufficient.
As of myself, I used Python:
import sys
import json
data = json.load(sys.stdin)
data["Section_B"]["ws/abc-Location02"] = 9999
json.dump(data, sys.stdout,indent=True)
Assuming that file stored as "change.py"
# Original data (proper JSON)
$ cat data.json
{
"Section_A": {
"ws/abc-Location01": 24,
"ws/abc-Location02": 67,
"ws/abc-Location03": 101
},
"Section_B": {
"ws/abc-Location01": 33,
"ws/abc-Location02": 59,
"ws/abc-Location03": 92
}
}
# Pipe original data to our filter
$ cat data.json | python change.py
{
"Section_A": {
"ws/abc-Location01": 24,
"ws/abc-Location02": 67,
"ws/abc-Location03": 101
},
"Section_B": {
"ws/abc-Location01": 33,
"ws/abc-Location02": 9999,
"ws/abc-Location03": 92
}
}
If you want a shell only solution, sed
might be sufficient:
cat data.json | sed '/Section_B/,/}/{/Location02/s/:[^,]*/ 9999/}'
This is obviously shorter; but much more fragile too...
On the bright side, this will process JSON-like files as well a proper JSON (assuming formated as in your example).