Reputation: 328
I have a .json file where I need to replace a particular value:
{
"partitions": [
{
"filesystem_type": "FAT",
"label": "boot",
"mkfs_options": "-F 32",
"partition_size_nominal": 256,
"uncompressed_tarball_size": 53,
"want_maximised": false,
"sha256sum": "dd54710df7756e64cff43bba3c930e85c97f22d43e0d32bc37396be34166c148"
},
{
"filesystem_type": "ext4",
"label": "root",
"mkfs_options": "-O ^huge_file",
"partition_size_nominal": 1415,
"uncompressed_tarball_size": 1015,
"want_maximised": true,
"sha256sum": "bb0892d4a028ae9d282849d05adf851fe54173013f6331e74a4bdd07e4af8dab"
}
]
}
Particularly, I need to replace the partition_size_nominal
tag of the root
partition
The best way I could come up with is very hacky:
#!/bin/bash
fname=partitions.json
tag=partition_size_nominal
newvalue=2942
rootline=$(sed -n '/label.*root/=' $fname | head -1)
blockstart=$(sed -n '1,'"$rootline"'p' $fname | tac | sed -n '/{/=' | head -1)
blockstart=$(( rootline - blockstart + 1 ))
blockend=$(sed -n ''"$blockstart"',$p' $fname | sed -n '/}/=' | head -1)
blockend=$(( blockstart + blockend -1 ))
sed ''"$blockstart"','"$blockend"'s/"'"$tag"'".*/"'"$tag"'": '"$newvalue"',/' $fname
The basic idea is: find root label tag, search backward for beginning of block, search forward for end of block, replace the partition size tag value within block. I can't assume that the partition size tag is after the label tag, hence searching for the block bounds.
Does anyone know of a more elegant solution for this type of situation?
Upvotes: 0
Views: 182
Reputation: 530950
Minor changes in whitespace aside, you can use jq
for this:
fname=partitions.json
tag=partition_size_nominal
newvalue=2942
jq --arg t "$tag" --argjson v "$newvalue" \
'(.partitions[] | select(.label == "root"))[$t] |= $v' "$fname"
It looks a little intimidating, but it's not too complicated. jq
operates using filters; every filter takes an input, does something, and produces an output.
The top-level operator in this expression, |=
, combines its left-hand and right-hand operands into a new filter which takes the incoming JSON object as input.
partition_size_nominal
element of the object whose label
element has the value "root"
.The output of |=
is then the original object, with the output of the left-hand operand as the target to which the value of the right-hand operand is assigned..
--arg t "$tag"
binds the result of JSON-encoding "$tag"
to the variable named t
.
--argjson v "$newvalue"
assumes that "$newvalue"
is already properly encoded as a JSON value, and binds it to the variable named v
.
Upvotes: 1
Reputation: 52334
As mentioned, jq
is the way to go when working with JSON in a shell script. This:
jq --arg size "$newvalue" '
.partitions |= [.[] | if .label == "root" then
.partition_size_nominal |= ($size | tonumber)
else
.
end]' "$fname"
outputs
{
"partitions": [
{
"filesystem_type": "FAT",
"label": "boot",
"mkfs_options": "-F 32",
"partition_size_nominal": 256,
"uncompressed_tarball_size": 53,
"want_maximised": false,
"sha256sum": "dd54710df7756e64cff43bba3c930e85c97f22d43e0d32bc37396be34166c148"
},
{
"filesystem_type": "ext4",
"label": "root",
"mkfs_options": "-O ^huge_file",
"partition_size_nominal": 2942,
"uncompressed_tarball_size": 1015,
"want_maximised": true,
"sha256sum": "bb0892d4a028ae9d282849d05adf851fe54173013f6331e74a4bdd07e4af8dab"
}
]
}
(Assuming the variables set in your shell script)
Upvotes: 1